Convert skpCaptureEnabled compile time flag into a property

Convert SkiaPipeline::skpCaptureEnabled into a system property.
Add ability to capture drawing in layers. Add ability to capture
animations/sequence of frames. Fix crash when recording a
TextureView.

Test: Ran capture script.
Change-Id: I463eecf6ec90a601a6cc172ad1901bd4bcc86ac8
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 3b59bb1..588c4de 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -58,6 +58,7 @@
 
 bool Properties::filterOutTestOverhead = false;
 bool Properties::disableVsync = false;
+bool Properties::skpCaptureEnabled = false;
 
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
@@ -128,6 +129,9 @@
 
     filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
 
+    skpCaptureEnabled = property_get_bool("ro.debuggable", false)
+            && property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false);
+
     return (prevDebugLayersUpdates != debugLayersUpdates)
             || (prevDebugOverdraw != debugOverdraw)
             || (prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 0fe4c77..077e7ae 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -165,6 +165,22 @@
  */
 #define PROPERTY_RENDERER "debug.hwui.renderer"
 
+/**
+ * Allows to collect a recording of Skia drawing commands.
+ */
+#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
+
+
+/**
+ * Defines how many frames in a sequence to capture.
+ */
+#define PROPERTY_CAPTURE_SKP_FRAMES  "debug.hwui.capture_skp_frames"
+
+/**
+ * File name and location, where a SKP recording will be saved.
+ */
+#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -254,6 +270,8 @@
     // created after changing this.
     static bool disableVsync;
 
+    static bool skpCaptureEnabled;
+
     // Used for testing only to change the render pipeline.
 #ifdef HWUI_GLES_WRAP_ENABLED
     static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3b72a8b..def04e1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -35,6 +35,10 @@
 }
 
 bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer) {
+    if (context == nullptr) {
+        SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+        return false;
+    }
     // transform the matrix based on the layer
     SkMatrix layerTransform;
     layer->getTransform().copyTo(layerTransform);
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 47dee9d..ca93925 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -101,7 +101,7 @@
 
 void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
     RenderNode* renderNode = mRenderNode.get();
-    if (SkiaPipeline::skpCaptureEnabled()) {
+    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
         SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
         canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
     }
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index c4bd1e1..88ac10a 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -106,7 +106,7 @@
 
             const Rect& layerDamage = layers.entries()[i].damage;
 
-            SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+            SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
 
             int saveCount = layerCanvas->save();
             SkASSERT(saveCount == 1);
@@ -133,9 +133,11 @@
             layerCanvas->restoreToCount(saveCount);
             mLightCenter = savedLightCenter;
 
+            endCapture(layerNode->getLayerSurface());
+
             // cache the current context so that we can defer flushing it until
             // either all the layers have been rendered or the context changes
-            GrContext* currentContext = layerCanvas->getGrContext();
+            GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
             if (cachedContext.get() != currentContext) {
                 if (cachedContext.get()) {
                     cachedContext->flush();
@@ -221,6 +223,91 @@
     }
 }
 
+class SkiaPipeline::SavePictureProcessor : public TaskProcessor<bool> {
+public:
+    explicit SavePictureProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {}
+
+    struct SavePictureTask : public Task<bool> {
+        sk_sp<SkData> data;
+        std::string filename;
+    };
+
+    void savePicture(const sk_sp<SkData>& data, const std::string& filename) {
+        sp<SavePictureTask> task(new SavePictureTask());
+        task->data = data;
+        task->filename = filename;
+        TaskProcessor<bool>::add(task);
+    }
+
+    virtual void onProcess(const sp<Task<bool> >& task) override {
+        SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
+
+        if (0 == access(t->filename.c_str(), F_OK)) {
+            task->setResult(false);
+            return;
+        }
+
+        SkFILEWStream stream(t->filename.c_str());
+        if (stream.isValid()) {
+            stream.write(t->data->data(), t->data->size());
+            stream.flush();
+            SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s",
+                    stream.bytesWritten(), t->filename.c_str());
+        }
+
+        task->setResult(true);
+    }
+};
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+        bool recordingPicture = mCaptureSequence > 0;
+        char prop[PROPERTY_VALUE_MAX] = {'\0'};
+        if (!recordingPicture) {
+            property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
+            recordingPicture = prop[0] != '0'
+                    && mCapturedFile != prop; //ensure we capture only once per filename
+            if (recordingPicture) {
+                mCapturedFile = prop;
+                mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
+            }
+        }
+        if (recordingPicture) {
+            mRecorder.reset(new SkPictureRecorder());
+            return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+                    SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+        }
+    }
+    return surface->getCanvas();
+}
+
+void SkiaPipeline::endCapture(SkSurface* surface) {
+    if (CC_UNLIKELY(mRecorder.get())) {
+        sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
+        surface->getCanvas()->drawPicture(picture);
+        if (picture->approximateOpCount() > 0) {
+            SkDynamicMemoryWStream stream;
+            PngPixelSerializer serializer;
+            picture->serialize(&stream, &serializer);
+
+            //offload saving to file in a different thread
+            if (!mSavePictureProcessor.get()) {
+                TaskManager* taskManager = getTaskManager();
+                mSavePictureProcessor = new SavePictureProcessor(
+                        taskManager->canRunTasks() ? taskManager : nullptr);
+            }
+            if (1 == mCaptureSequence) {
+                mSavePictureProcessor->savePicture(stream.detachAsData(), mCapturedFile);
+            } else {
+                mSavePictureProcessor->savePicture(stream.detachAsData(),
+                                        mCapturedFile + "_" + std::to_string(mCaptureSequence));
+            }
+            mCaptureSequence--;
+        }
+        mRecorder.reset();
+    }
+}
+
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
         const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
         const Rect &contentDrawBounds, sk_sp<SkSurface> surface) {
@@ -230,44 +317,21 @@
     // draw all layers up front
     renderLayersImpl(layers, opaque, wideColorGamut);
 
-    // initialize the canvas for the current frame
-    SkCanvas* canvas = surface->getCanvas();
-
+    // initialize the canvas for the current frame, that might be a recording canvas if SKP
+    // capture is enabled.
     std::unique_ptr<SkPictureRecorder> recorder;
-    bool recordingPicture = false;
-    char prop[PROPERTY_VALUE_MAX];
-    if (skpCaptureEnabled()) {
-        property_get("debug.hwui.capture_frame_as_skp", prop, "0");
-        recordingPicture = prop[0] != '0' && access(prop, F_OK) != 0;
-        if (recordingPicture) {
-            recorder.reset(new SkPictureRecorder());
-            canvas = recorder->beginRecording(surface->width(), surface->height(),
-                    nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
-        }
-    }
+    SkCanvas* canvas = tryCapture(surface.get());
 
     renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);
 
-    if (skpCaptureEnabled() && recordingPicture) {
-        sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
-        if (picture->approximateOpCount() > 0) {
-            SkFILEWStream stream(prop);
-            if (stream.isValid()) {
-                PngPixelSerializer serializer;
-                picture->serialize(&stream, &serializer);
-                stream.flush();
-                SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop);
-            }
-        }
-        surface->getCanvas()->drawPicture(picture);
-    }
+    endCapture(surface.get());
 
     if (CC_UNLIKELY(Properties::debugOverdraw)) {
         renderOverdraw(layers, clip, nodes, contentDrawBounds, surface);
     }
 
     ATRACE_NAME("flush commands");
-    canvas->flush();
+    surface->getCanvas()->flush();
 }
 
 namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 3e6ae30..d1faa8a 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -21,6 +21,8 @@
 #include "renderthread/IRenderPipeline.h"
 #include <SkSurface.h>
 
+class SkPictureRecorder;
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -55,9 +57,7 @@
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
 
-    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
-
-    static bool skpCaptureEnabled() { return false; }
+    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
 
     static float getLightRadius() {
         if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) {
@@ -126,12 +126,38 @@
      */
     void renderVectorDrawableCache();
 
+    SkCanvas* tryCapture(SkSurface* surface);
+    void endCapture(SkSurface* surface);
+
     std::vector<sk_sp<SkImage>> mPinnedImages;
 
     /**
      *  populated by prepareTree with dirty VDs
      */
     std::vector<VectorDrawableRoot*> mVectorDrawables;
+
+
+    // Block of properties used only for debugging to record a SkPicture and save it in a file.
+    /**
+     * mCapturedFile is used to enforce we don't capture more than once for a given name (cause
+     * permissions don't allow to reset a property from render thread).
+     */
+    std::string mCapturedFile;
+    /**
+     *  mCaptureSequence counts how many frames are left to take in the sequence.
+     */
+    int mCaptureSequence = 0;
+    /**
+     *  mSavePictureProcessor is used to run the file saving code in a separate thread.
+     */
+    class SavePictureProcessor;
+    sp<SavePictureProcessor> mSavePictureProcessor;
+    /**
+     *  mRecorder holds the current picture recorder. We could store it on the stack to support
+     *  parallel tryCapture calls (not really needed).
+     */
+    std::unique_ptr<SkPictureRecorder> mRecorder;
+
     static float mLightRadius;
     static uint8_t mAmbientShadowAlpha;
     static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/tests/scripts/skp-capture.sh b/libs/hwui/tests/scripts/skp-capture.sh
new file mode 100755
index 0000000..54fa229
--- /dev/null
+++ b/libs/hwui/tests/scripts/skp-capture.sh
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if [ -z "$1" ]; then
+    printf 'Usage:\n    skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n'
+    printf "Use \`adb shell 'pm list packages'\` to get a listing.\n\n"
+    exit 1
+fi
+if ! command -v adb > /dev/null 2>&1; then
+    if [ -x "${ANDROID_SDK_ROOT}/platform-tools/adb" ]; then
+        adb() {
+            "${ANDROID_SDK_ROOT}/platform-tools/adb" "$@"
+        }
+    else
+        echo 'adb missing'
+        exit 2
+    fi
+fi
+phase1_timeout_seconds=15
+phase2_timeout_seconds=60
+package="$1"
+filename="$(date '+%H%M%S').skp"
+remote_path="/data/data/${package}/cache/${filename}"
+local_path_prefix="$(date '+%Y-%m-%d_%H%M%S')_${package}"
+local_path="${local_path_prefix}.skp"
+enable_capture_key='debug.hwui.capture_skp_enabled'
+enable_capture_value=$(adb shell "getprop '${enable_capture_key}'")
+#printf 'captureflag=' "$enable_capture_value" '\n'
+if [ -z "$enable_capture_value" ]; then
+    printf 'Capture SKP property need to be enabled first. Please use\n'
+    printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n"
+    printf "the process.\n\n"
+    exit 1
+fi
+if [ ! -z "$2" ]; then
+    adb shell "setprop 'debug.hwui.capture_skp_frames' $2"
+fi
+filename_key='debug.hwui.skp_filename'
+adb shell "setprop '${filename_key}' '${remote_path}'"
+spin() {
+    case "$spin" in
+         1) printf '\b|';;
+         2) printf '\b\\';;
+         3) printf '\b-';;
+         *) printf '\b/';;
+    esac
+    spin=$(( ( ${spin:-0} + 1 ) % 4 ))
+    sleep $1
+}
+
+banner() {
+    printf '\n=====================\n'
+    printf '   %s' "$*"
+    printf '\n=====================\n'
+}
+banner '...WAITING...'
+adb_test_exist() {
+    test '0' = "$(adb shell "test -e \"$1\"; echo \$?")";
+}
+timeout=$(( $(date +%s) + $phase1_timeout_seconds))
+while ! adb_test_exist "$remote_path"; do
+    spin 0.05
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+#read -n1 -r -p "Press any key to continue..." key
+
+banner '...SAVING...'
+adb_test_file_nonzero() {
+    # grab first byte of `du` output
+    X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")"
+    test "$X" && test "$X" -ne 0
+}
+#adb_filesize() {
+#    adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
+#}
+timeout=$(( $(date +%s) + $phase2_timeout_seconds))
+while ! adb_test_file_nonzero "$remote_path"; do
+    spin 0.05
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+adb shell "setprop '${filename_key}' ''"
+
+i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo
+
+adb pull "$remote_path" "$local_path"
+if ! [ -f "$local_path" ] ; then
+    printf "something went wrong with `adb pull`."
+    exit 4
+fi
+adb shell rm "$remote_path"
+printf '\nSKP saved to file:\n    %s\n\n'  "$local_path"
+if [ ! -z "$2" ]; then
+    bridge="_"
+    adb shell "setprop 'debug.hwui.capture_skp_frames' ''"
+    for i in $(seq 2 $2); do
+        adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp"
+        adb shell rm "${remote_path}_${i}"
+    done
+fi
+
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 4c3e182..d182d78 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -22,6 +22,7 @@
 #include "IContextFactory.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "pipeline/skia/SkiaPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 #include "renderthread/CanvasContext.h"
 #include "tests/common/TestUtils.h"
@@ -335,7 +336,7 @@
     EXPECT_EQ(3, canvas.getIndex());
 }
 
-RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
     /* R is backward projected on B and C is a layer.
                 A
                / \
@@ -450,7 +451,8 @@
     LayerUpdateQueue layerUpdateQueue;
     layerUpdateQueue.enqueueLayerWithDamage(child.get(),
             android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
-    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false);
+    auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+    pipeline->renderLayersImpl(layerUpdateQueue, true, false);
     EXPECT_EQ(1, drawCounter);  //assert index 0 is drawn on the layer
 
     RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);