Merge "Add continuous SKP capture test api"
This commit is contained in:
@@ -14619,8 +14619,8 @@ package android.graphics {
|
||||
public class Picture {
|
||||
ctor public Picture();
|
||||
ctor public Picture(android.graphics.Picture);
|
||||
method public android.graphics.Canvas beginRecording(int, int);
|
||||
method public void draw(android.graphics.Canvas);
|
||||
method @NonNull public android.graphics.Canvas beginRecording(int, int);
|
||||
method public void draw(@NonNull android.graphics.Canvas);
|
||||
method public void endRecording();
|
||||
method public int getHeight();
|
||||
method public int getWidth();
|
||||
@@ -14872,7 +14872,7 @@ package android.graphics {
|
||||
}
|
||||
|
||||
public final class RenderNode {
|
||||
ctor public RenderNode(String);
|
||||
ctor public RenderNode(@Nullable String);
|
||||
method public int computeApproximateMemoryUsage();
|
||||
method public void discardDisplayList();
|
||||
method public void endRecording();
|
||||
|
||||
@@ -239,8 +239,8 @@ package android.graphics {
|
||||
}
|
||||
|
||||
public class Picture {
|
||||
method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream);
|
||||
method @Deprecated public void writeToStream(java.io.OutputStream);
|
||||
method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream);
|
||||
method @Deprecated public void writeToStream(@NonNull java.io.OutputStream);
|
||||
}
|
||||
|
||||
@Deprecated public class PixelXorXfermode extends android.graphics.Xfermode {
|
||||
|
||||
@@ -2273,6 +2273,10 @@ package android.view {
|
||||
method public static int getLongPressTooltipHideTimeout();
|
||||
}
|
||||
|
||||
public class ViewDebug {
|
||||
method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>);
|
||||
}
|
||||
|
||||
public interface WindowManager extends android.view.ViewManager {
|
||||
method public default void setShouldShowIme(int, boolean);
|
||||
method public default void setShouldShowSystemDecors(int, boolean);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.HardwareRenderer;
|
||||
import android.graphics.Picture;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RecordingCanvas;
|
||||
import android.graphics.Rect;
|
||||
@@ -553,6 +554,10 @@ public final class ThreadedRenderer extends HardwareRenderer {
|
||||
dumpProfileInfo(fd, flags);
|
||||
}
|
||||
|
||||
Picture captureRenderingCommands() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadSystemProperties() {
|
||||
boolean changed = super.loadSystemProperties();
|
||||
|
||||
@@ -17,17 +17,21 @@
|
||||
package android.view;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.annotation.UnsupportedAppUsage;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.HardwareRenderer;
|
||||
import android.graphics.Picture;
|
||||
import android.graphics.RecordingCanvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RenderNode;
|
||||
import android.os.Debug;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
@@ -48,16 +52,20 @@ import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
|
||||
@@ -741,6 +749,123 @@ public class ViewDebug {
|
||||
root.getViewRootImpl().outputDisplayList(target);
|
||||
}
|
||||
|
||||
private static class PictureCallbackHandler implements AutoCloseable,
|
||||
HardwareRenderer.PictureCapturedCallback, Runnable {
|
||||
private final HardwareRenderer mRenderer;
|
||||
private final Function<Picture, Boolean> mCallback;
|
||||
private final Executor mExecutor;
|
||||
private final ReentrantLock mLock = new ReentrantLock(false);
|
||||
private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
|
||||
private boolean mStopListening;
|
||||
private Thread mRenderThread;
|
||||
|
||||
private PictureCallbackHandler(HardwareRenderer renderer,
|
||||
Function<Picture, Boolean> callback, Executor executor) {
|
||||
mRenderer = renderer;
|
||||
mCallback = callback;
|
||||
mExecutor = executor;
|
||||
mRenderer.setPictureCaptureCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mLock.lock();
|
||||
mStopListening = true;
|
||||
mLock.unlock();
|
||||
mRenderer.setPictureCaptureCallback(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureCaptured(Picture picture) {
|
||||
mLock.lock();
|
||||
if (mStopListening) {
|
||||
mLock.unlock();
|
||||
mRenderer.setPictureCaptureCallback(null);
|
||||
return;
|
||||
}
|
||||
if (mRenderThread == null) {
|
||||
mRenderThread = Thread.currentThread();
|
||||
}
|
||||
Picture toDestroy = null;
|
||||
if (mQueue.size() == 3) {
|
||||
toDestroy = mQueue.removeLast();
|
||||
}
|
||||
mQueue.add(picture);
|
||||
mLock.unlock();
|
||||
if (toDestroy == null) {
|
||||
mExecutor.execute(this);
|
||||
} else {
|
||||
toDestroy.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mLock.lock();
|
||||
final Picture picture = mQueue.poll();
|
||||
final boolean isStopped = mStopListening;
|
||||
mLock.unlock();
|
||||
if (Thread.currentThread() == mRenderThread) {
|
||||
close();
|
||||
throw new IllegalStateException(
|
||||
"ViewDebug#startRenderingCommandsCapture must be given an executor that "
|
||||
+ "invokes asynchronously");
|
||||
}
|
||||
if (isStopped) {
|
||||
picture.close();
|
||||
return;
|
||||
}
|
||||
final boolean keepReceiving = mCallback.apply(picture);
|
||||
if (!keepReceiving) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins capturing the entire rendering commands for the view tree referenced by the given
|
||||
* view. The view passed may be any View in the tree as long as it is attached. That is,
|
||||
* {@link View#isAttachedToWindow()} must be true.
|
||||
*
|
||||
* Every time a frame is rendered a Picture will be passed to the given callback via the given
|
||||
* executor. As long as the callback returns 'true' it will continue to receive new frames.
|
||||
* The system will only invoke the callback at a rate that the callback is able to keep up with.
|
||||
* That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
|
||||
* then the callback will only receive 33% of the frames produced.
|
||||
*
|
||||
* This method must be called on the same thread as the View tree.
|
||||
*
|
||||
* @param tree The View tree to capture the rendering commands.
|
||||
* @param callback The callback to invoke on every frame produced. Should return true to
|
||||
* continue receiving new frames, false to stop capturing.
|
||||
* @param executor The executor to invoke the callback on. Recommend using a background thread
|
||||
* to avoid stalling the UI thread. Must be an asynchronous invoke or an
|
||||
* exception will be thrown.
|
||||
* @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
|
||||
* that the callback may continue to receive another frame or two depending on thread timings.
|
||||
* Returns null if the capture stream cannot be started, such as if there's no
|
||||
* HardwareRenderer for the given view tree.
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@Nullable
|
||||
public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
|
||||
Function<Picture, Boolean> callback) {
|
||||
final View.AttachInfo attachInfo = tree.mAttachInfo;
|
||||
if (attachInfo == null) {
|
||||
throw new IllegalArgumentException("Given view isn't attached");
|
||||
}
|
||||
if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
|
||||
throw new IllegalStateException("Called on the wrong thread."
|
||||
+ " Must be called on the thread that owns the given View");
|
||||
}
|
||||
final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
|
||||
if (renderer != null) {
|
||||
return new PictureCallbackHandler(renderer, callback, executor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void capture(View root, final OutputStream clientStream, String parameter)
|
||||
throws IOException {
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ Picture::Picture(const Picture* src) {
|
||||
}
|
||||
}
|
||||
|
||||
Picture::Picture(sk_sp<SkPicture>&& src) {
|
||||
mPicture = std::move(src);
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
}
|
||||
|
||||
Canvas* Picture::beginRecording(int width, int height) {
|
||||
mPicture.reset(NULL);
|
||||
mRecorder.reset(new SkPictureRecorder);
|
||||
|
||||
@@ -37,6 +37,7 @@ class Canvas;
|
||||
class Picture {
|
||||
public:
|
||||
explicit Picture(const Picture* src = NULL);
|
||||
explicit Picture(sk_sp<SkPicture>&& src);
|
||||
|
||||
Canvas* beginRecording(int width, int height);
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include <FrameInfo.h>
|
||||
#include <FrameMetricsObserver.h>
|
||||
#include <IContextFactory.h>
|
||||
#include <Picture.h>
|
||||
#include <Properties.h>
|
||||
#include <PropertyValuesAnimatorSet.h>
|
||||
#include <RenderNode.h>
|
||||
@@ -70,6 +71,11 @@ struct {
|
||||
jmethodID callback;
|
||||
} gFrameMetricsObserverClassInfo;
|
||||
|
||||
struct {
|
||||
jclass clazz;
|
||||
jmethodID invokePictureCapturedCallback;
|
||||
} gHardwareRenderer;
|
||||
|
||||
struct {
|
||||
jmethodID onFrameDraw;
|
||||
} gFrameDrawingCallback;
|
||||
@@ -905,6 +911,27 @@ private:
|
||||
jobject mObject;
|
||||
};
|
||||
|
||||
static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
|
||||
jobject clazz, jlong proxyPtr, jobject pictureCallback) {
|
||||
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
|
||||
if (!pictureCallback) {
|
||||
proxy->setPictureCapturedCallback(nullptr);
|
||||
} else {
|
||||
JavaVM* vm = nullptr;
|
||||
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
|
||||
auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
|
||||
env->NewGlobalRef(pictureCallback));
|
||||
proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) {
|
||||
JNIEnv* env = getenv(globalCallbackRef->vm());
|
||||
Picture* wrapper = new Picture{std::move(picture)};
|
||||
env->CallStaticVoidMethod(gHardwareRenderer.clazz,
|
||||
gHardwareRenderer.invokePictureCapturedCallback,
|
||||
static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
|
||||
globalCallbackRef->object());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
|
||||
jobject clazz, jlong proxyPtr, jobject frameCallback) {
|
||||
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
|
||||
@@ -1145,6 +1172,8 @@ static const JNINativeMethod gMethods[] = {
|
||||
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
|
||||
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
|
||||
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
|
||||
{ "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
|
||||
(void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI },
|
||||
{ "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V",
|
||||
(void*)android_view_ThreadedRenderer_setFrameCallback},
|
||||
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
|
||||
@@ -1198,6 +1227,13 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
|
||||
gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
|
||||
env, metricsClass, "mTimingData", "[J");
|
||||
|
||||
jclass hardwareRenderer = FindClassOrDie(env,
|
||||
"android/graphics/HardwareRenderer");
|
||||
gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer));
|
||||
gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
|
||||
"invokePictureCapturedCallback",
|
||||
"(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
|
||||
|
||||
jclass frameCallbackClass = FindClassOrDie(env,
|
||||
"android/graphics/HardwareRenderer$FrameDrawingCallback");
|
||||
gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.FloatRange;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.os.IBinder;
|
||||
@@ -667,6 +668,17 @@ public class HardwareRenderer {
|
||||
nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
|
||||
nSetPictureCaptureCallback(mNativeProxy, callback);
|
||||
}
|
||||
|
||||
/** called by native */
|
||||
static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
|
||||
Picture picture = new Picture(picturePtr);
|
||||
callback.onPictureCaptured(picture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to receive callbacks when a frame is being drawn.
|
||||
*
|
||||
@@ -695,6 +707,17 @@ public class HardwareRenderer {
|
||||
void onFrameComplete(long frameNr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for listening to picture captures
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public interface PictureCapturedCallback {
|
||||
/** @hide */
|
||||
@TestApi
|
||||
void onPictureCaptured(Picture picture);
|
||||
}
|
||||
|
||||
private static void validateAlpha(float alpha, String argumentName) {
|
||||
if (!(alpha >= 0.0f && alpha <= 1.0f)) {
|
||||
throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
|
||||
@@ -998,6 +1021,9 @@ public class HardwareRenderer {
|
||||
private static native void nSetContentDrawBounds(long nativeProxy, int left,
|
||||
int top, int right, int bottom);
|
||||
|
||||
private static native void nSetPictureCaptureCallback(long nativeProxy,
|
||||
PictureCapturedCallback callback);
|
||||
|
||||
private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
|
||||
|
||||
private static native void nSetFrameCompleteCallback(long nativeProxy,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.graphics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UnsupportedAppUsage;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -34,7 +35,8 @@ import java.io.OutputStream;
|
||||
*/
|
||||
public class Picture {
|
||||
private PictureCanvas mRecordingCanvas;
|
||||
@UnsupportedAppUsage
|
||||
// TODO: Figure out if this was a false-positive
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28)
|
||||
private long mNativePicture;
|
||||
private boolean mRequiresHwAcceleration;
|
||||
|
||||
@@ -56,23 +58,43 @@ public class Picture {
|
||||
this(nativeConstructor(src != null ? src.mNativePicture : 0));
|
||||
}
|
||||
|
||||
private Picture(long nativePicture) {
|
||||
/** @hide */
|
||||
public Picture(long nativePicture) {
|
||||
if (nativePicture == 0) {
|
||||
throw new RuntimeException();
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
mNativePicture = nativePicture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately releases the backing data of the Picture. This object will no longer
|
||||
* be usable after calling this, and any further calls on the Picture will throw an
|
||||
* IllegalStateException.
|
||||
* // TODO: Support?
|
||||
* @hide
|
||||
*/
|
||||
public void close() {
|
||||
if (mNativePicture != 0) {
|
||||
nativeDestructor(mNativePicture);
|
||||
mNativePicture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
nativeDestructor(mNativePicture);
|
||||
mNativePicture = 0;
|
||||
close();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyValid() {
|
||||
if (mNativePicture == 0) {
|
||||
throw new IllegalStateException("Picture is destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To record a picture, call beginRecording() and then draw into the Canvas
|
||||
* that is returned. Nothing we appear on screen, but all of the draw
|
||||
@@ -81,7 +103,9 @@ public class Picture {
|
||||
* that was returned must no longer be used, and nothing should be drawn
|
||||
* into it.
|
||||
*/
|
||||
@NonNull
|
||||
public Canvas beginRecording(int width, int height) {
|
||||
verifyValid();
|
||||
if (mRecordingCanvas != null) {
|
||||
throw new IllegalStateException("Picture already recording, must call #endRecording()");
|
||||
}
|
||||
@@ -98,6 +122,7 @@ public class Picture {
|
||||
* or {@link Canvas#drawPicture(Picture)} is called.
|
||||
*/
|
||||
public void endRecording() {
|
||||
verifyValid();
|
||||
if (mRecordingCanvas != null) {
|
||||
mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
|
||||
mRecordingCanvas = null;
|
||||
@@ -110,7 +135,8 @@ public class Picture {
|
||||
* does not reflect (per se) the content of the picture.
|
||||
*/
|
||||
public int getWidth() {
|
||||
return nativeGetWidth(mNativePicture);
|
||||
verifyValid();
|
||||
return nativeGetWidth(mNativePicture);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +144,8 @@ public class Picture {
|
||||
* does not reflect (per se) the content of the picture.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return nativeGetHeight(mNativePicture);
|
||||
verifyValid();
|
||||
return nativeGetHeight(mNativePicture);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,6 +160,7 @@ public class Picture {
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean requiresHardwareAcceleration() {
|
||||
verifyValid();
|
||||
return mRequiresHwAcceleration;
|
||||
}
|
||||
|
||||
@@ -149,7 +177,8 @@ public class Picture {
|
||||
*
|
||||
* @param canvas The picture is drawn to this canvas
|
||||
*/
|
||||
public void draw(Canvas canvas) {
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
verifyValid();
|
||||
if (mRecordingCanvas != null) {
|
||||
endRecording();
|
||||
}
|
||||
@@ -172,7 +201,7 @@ public class Picture {
|
||||
* raw or compressed pixels.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Picture createFromStream(InputStream stream) {
|
||||
public static Picture createFromStream(@NonNull InputStream stream) {
|
||||
return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
|
||||
}
|
||||
|
||||
@@ -188,10 +217,11 @@ public class Picture {
|
||||
* Bitmap from which you can persist it as raw or compressed pixels.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeToStream(OutputStream stream) {
|
||||
public void writeToStream(@NonNull OutputStream stream) {
|
||||
verifyValid();
|
||||
// do explicit check before calling the native method
|
||||
if (stream == null) {
|
||||
throw new NullPointerException();
|
||||
throw new IllegalArgumentException("stream cannot be null");
|
||||
}
|
||||
if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
|
||||
throw new RuntimeException();
|
||||
|
||||
@@ -184,7 +184,7 @@ public final class RenderNode {
|
||||
*
|
||||
* @param name The name of the RenderNode, used for debugging purpose. May be null.
|
||||
*/
|
||||
public RenderNode(String name) {
|
||||
public RenderNode(@Nullable String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -551,6 +551,19 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint)
|
||||
SkPaint paint = inPaint;
|
||||
paint.setAlpha(mProperties.getRootAlpha() * 255);
|
||||
|
||||
if (canvas->getGrContext() == nullptr) {
|
||||
// Recording to picture, don't use the SkSurface which won't work off of renderthread.
|
||||
Bitmap& bitmap = getBitmapUpdateIfDirty();
|
||||
SkBitmap skiaBitmap;
|
||||
bitmap.getSkBitmap(&skiaBitmap);
|
||||
|
||||
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
|
||||
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
|
||||
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
|
||||
&paint, SkCanvas::kFast_SrcRectConstraint);
|
||||
return;
|
||||
}
|
||||
|
||||
SkRect src;
|
||||
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
|
||||
if (vdSurface) {
|
||||
|
||||
@@ -74,7 +74,13 @@ static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSiz
|
||||
|
||||
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
|
||||
if (canvas->getGrContext() == nullptr) {
|
||||
SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
|
||||
// We're dumping a picture, render a light-blue rectangle instead
|
||||
// TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
|
||||
// seem to have a default typeface that works. We only ever use drawGlyphs, which
|
||||
// requires going through minikin & hwui's canvas which we don't have here.
|
||||
SkPaint paint;
|
||||
paint.setColor(0xFF81D4FA);
|
||||
canvas->drawRect(mBounds, paint);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque)
|
||||
|
||||
const Rect& layerDamage = layers.entries()[i].damage;
|
||||
|
||||
SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
|
||||
SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
|
||||
|
||||
int saveCount = layerCanvas->save();
|
||||
SkASSERT(saveCount == 1);
|
||||
@@ -139,8 +139,6 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque)
|
||||
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 = layerNode->getLayerSurface()->getCanvas()->getGrContext();
|
||||
@@ -244,6 +242,7 @@ public:
|
||||
}
|
||||
|
||||
virtual void onProcess(const sp<Task<bool>>& task) override {
|
||||
ATRACE_NAME("SavePictureTask");
|
||||
SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
|
||||
|
||||
if (0 == access(t->filename.c_str(), F_OK)) {
|
||||
@@ -265,46 +264,56 @@ public:
|
||||
|
||||
SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
|
||||
if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
|
||||
bool recordingPicture = mCaptureSequence > 0;
|
||||
char prop[PROPERTY_VALUE_MAX] = {'\0'};
|
||||
if (!recordingPicture) {
|
||||
if (mCaptureSequence <= 0) {
|
||||
property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
|
||||
recordingPicture = prop[0] != '0' &&
|
||||
mCapturedFile != prop; // ensure we capture only once per filename
|
||||
if (recordingPicture) {
|
||||
if (prop[0] != '0' && mCapturedFile != prop) {
|
||||
mCapturedFile = prop;
|
||||
mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
|
||||
}
|
||||
}
|
||||
if (recordingPicture) {
|
||||
if (mCaptureSequence > 0 || mPictureCapturedCallback) {
|
||||
mRecorder.reset(new SkPictureRecorder());
|
||||
return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
|
||||
SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
|
||||
SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
|
||||
SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
|
||||
mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
|
||||
mNwayCanvas->addCanvas(surface->getCanvas());
|
||||
mNwayCanvas->addCanvas(pictureCanvas);
|
||||
return mNwayCanvas.get();
|
||||
}
|
||||
}
|
||||
return surface->getCanvas();
|
||||
}
|
||||
|
||||
void SkiaPipeline::endCapture(SkSurface* surface) {
|
||||
mNwayCanvas.reset();
|
||||
if (CC_UNLIKELY(mRecorder.get())) {
|
||||
ATRACE_CALL();
|
||||
sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
|
||||
surface->getCanvas()->drawPicture(picture);
|
||||
if (picture->approximateOpCount() > 0) {
|
||||
auto data = picture->serialize();
|
||||
if (mCaptureSequence > 0) {
|
||||
ATRACE_BEGIN("picture->serialize");
|
||||
auto data = picture->serialize();
|
||||
ATRACE_END();
|
||||
|
||||
// offload saving to file in a different thread
|
||||
if (!mSavePictureProcessor.get()) {
|
||||
TaskManager* taskManager = getTaskManager();
|
||||
mSavePictureProcessor = new SavePictureProcessor(
|
||||
taskManager->canRunTasks() ? taskManager : nullptr);
|
||||
// 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(data, mCapturedFile);
|
||||
} else {
|
||||
mSavePictureProcessor->savePicture(
|
||||
data,
|
||||
mCapturedFile + "_" + std::to_string(mCaptureSequence));
|
||||
}
|
||||
mCaptureSequence--;
|
||||
}
|
||||
if (1 == mCaptureSequence) {
|
||||
mSavePictureProcessor->savePicture(data, mCapturedFile);
|
||||
} else {
|
||||
mSavePictureProcessor->savePicture(
|
||||
data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
|
||||
if (mPictureCapturedCallback) {
|
||||
std::invoke(mPictureCapturedCallback, std::move(picture));
|
||||
}
|
||||
mCaptureSequence--;
|
||||
}
|
||||
mRecorder.reset();
|
||||
}
|
||||
@@ -314,6 +323,11 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli
|
||||
const std::vector<sp<RenderNode>>& nodes, bool opaque,
|
||||
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
|
||||
const SkMatrix& preTransform) {
|
||||
bool previousSkpEnabled = Properties::skpCaptureEnabled;
|
||||
if (mPictureCapturedCallback) {
|
||||
Properties::skpCaptureEnabled = true;
|
||||
}
|
||||
|
||||
renderVectorDrawableCache();
|
||||
|
||||
// draw all layers up front
|
||||
@@ -334,6 +348,8 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli
|
||||
|
||||
ATRACE_NAME("flush commands");
|
||||
surface->getCanvas()->flush();
|
||||
|
||||
Properties::skpCaptureEnabled = previousSkpEnabled;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -105,6 +105,11 @@ public:
|
||||
mLightCenter = lightGeometry.center;
|
||||
}
|
||||
|
||||
void setPictureCapturedCallback(
|
||||
const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
|
||||
mPictureCapturedCallback = callback;
|
||||
}
|
||||
|
||||
protected:
|
||||
void dumpResourceCacheUsage() const;
|
||||
void setSurfaceColorProperties(renderthread::ColorMode colorMode);
|
||||
@@ -163,6 +168,8 @@ private:
|
||||
* parallel tryCapture calls (not really needed).
|
||||
*/
|
||||
std::unique_ptr<SkPictureRecorder> mRecorder;
|
||||
std::unique_ptr<SkNWayCanvas> mNwayCanvas;
|
||||
std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
|
||||
|
||||
static float mLightRadius;
|
||||
static uint8_t mAmbientShadowAlpha;
|
||||
|
||||
@@ -184,6 +184,10 @@ public:
|
||||
mFrameCompleteCallbacks.push_back(std::move(func));
|
||||
}
|
||||
|
||||
void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) {
|
||||
mRenderPipeline->setPictureCapturedCallback(callback);
|
||||
}
|
||||
|
||||
void setForceDark(bool enable) {
|
||||
mUseForceDark = enable;
|
||||
}
|
||||
|
||||
@@ -59,15 +59,15 @@ public:
|
||||
virtual MakeCurrentResult makeCurrent() = 0;
|
||||
virtual Frame getFrame() = 0;
|
||||
virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
|
||||
const LightGeometry& lightGeometry,
|
||||
LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
|
||||
bool opaque, const LightInfo& lightInfo,
|
||||
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
|
||||
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
|
||||
const std::vector<sp<RenderNode>>& renderNodes,
|
||||
FrameInfoVisualizer* profiler) = 0;
|
||||
virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
|
||||
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
|
||||
virtual DeferredLayerUpdater* createTextureLayer() = 0;
|
||||
virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
|
||||
virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
|
||||
ColorMode colorMode) = 0;
|
||||
virtual void onStop() = 0;
|
||||
virtual bool isSurfaceReady() = 0;
|
||||
virtual bool isContextReady() = 0;
|
||||
@@ -85,6 +85,8 @@ public:
|
||||
virtual SkColorType getSurfaceColorType() const = 0;
|
||||
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
|
||||
virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
|
||||
virtual void setPictureCapturedCallback(
|
||||
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
|
||||
|
||||
virtual ~IRenderPipeline() {}
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "Properties.h"
|
||||
#include "Readback.h"
|
||||
#include "Rect.h"
|
||||
#include "WebViewFunctorManager.h"
|
||||
#include "pipeline/skia/SkiaOpenGLPipeline.h"
|
||||
#include "pipeline/skia/VectorDrawableAtlas.h"
|
||||
#include "renderstate/RenderState.h"
|
||||
@@ -30,7 +31,6 @@
|
||||
#include "renderthread/RenderThread.h"
|
||||
#include "utils/Macros.h"
|
||||
#include "utils/TimeUtils.h"
|
||||
#include "WebViewFunctorManager.h"
|
||||
|
||||
#include <ui/GraphicBuffer.h>
|
||||
|
||||
@@ -147,9 +147,7 @@ void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
|
||||
void RenderProxy::destroyFunctor(int functor) {
|
||||
ATRACE_CALL();
|
||||
RenderThread& thread = RenderThread::getInstance();
|
||||
thread.queue().post([=]() {
|
||||
WebViewFunctorManager::instance().destroyFunctor(functor);
|
||||
});
|
||||
thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); });
|
||||
}
|
||||
|
||||
DeferredLayerUpdater* RenderProxy::createTextureLayer() {
|
||||
@@ -164,9 +162,9 @@ void RenderProxy::buildLayer(RenderNode* node) {
|
||||
|
||||
bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
|
||||
auto& thread = RenderThread::getInstance();
|
||||
return thread.queue().runSync(
|
||||
[&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap)
|
||||
== CopyResult::Success; });
|
||||
return thread.queue().runSync([&]() -> bool {
|
||||
return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success;
|
||||
});
|
||||
}
|
||||
|
||||
void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
|
||||
@@ -204,9 +202,8 @@ void RenderProxy::fence() {
|
||||
}
|
||||
|
||||
int RenderProxy::maxTextureSize() {
|
||||
static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() {
|
||||
return DeviceInfo::get()->maxTextureSize();
|
||||
});
|
||||
static int maxTextureSize = RenderThread::getInstance().queue().runSync(
|
||||
[]() { return DeviceInfo::get()->maxTextureSize(); });
|
||||
return maxTextureSize;
|
||||
}
|
||||
|
||||
@@ -281,6 +278,12 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom)
|
||||
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
|
||||
}
|
||||
|
||||
void RenderProxy::setPictureCapturedCallback(
|
||||
const std::function<void(sk_sp<SkPicture>&&)>& callback) {
|
||||
mRenderThread.queue().post(
|
||||
[ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); });
|
||||
}
|
||||
|
||||
void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
|
||||
mDrawFrameTask.setFrameCallback(std::move(callback));
|
||||
}
|
||||
@@ -302,9 +305,7 @@ void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr)
|
||||
}
|
||||
|
||||
void RenderProxy::setForceDark(bool enable) {
|
||||
mRenderThread.queue().post([this, enable]() {
|
||||
mContext->setForceDark(enable);
|
||||
});
|
||||
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
|
||||
}
|
||||
|
||||
int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
|
||||
@@ -348,9 +349,8 @@ int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
|
||||
// TODO: fix everything that hits this. We should never be triggering a readback ourselves.
|
||||
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
|
||||
} else {
|
||||
return thread.queue().runSync([&]() -> int {
|
||||
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
|
||||
});
|
||||
return thread.queue().runSync(
|
||||
[&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ public:
|
||||
ANDROID_API void removeRenderNode(RenderNode* node);
|
||||
ANDROID_API void drawRenderNode(RenderNode* node);
|
||||
ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
|
||||
ANDROID_API void setPictureCapturedCallback(
|
||||
const std::function<void(sk_sp<SkPicture>&&)>& callback);
|
||||
ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
|
||||
ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_PACKAGE_NAME := HwAccelerationTest
|
||||
LOCAL_PRIVATE_PLATFORM_APIS := true
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
|
||||
@@ -310,6 +310,15 @@
|
||||
<category android:name="com.android.test.hwui.TEST" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="PictureCaptureDemo"
|
||||
android:label="Debug/Picture Capture">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="com.android.test.hwui.TEST" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="SmallCircleActivity"
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.test.hwui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Picture;
|
||||
import android.os.Bundle;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewDebug;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class PictureCaptureDemo extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
final LinearLayout inner = new LinearLayout(this);
|
||||
inner.setOrientation(LinearLayout.HORIZONTAL);
|
||||
ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
|
||||
inner.addView(spinner,
|
||||
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inner.addView(new View(this), new LayoutParams(50, 1));
|
||||
|
||||
Picture picture = new Picture();
|
||||
Canvas canvas = picture.beginRecording(100, 100);
|
||||
canvas.drawColor(Color.RED);
|
||||
Paint paint = new Paint();
|
||||
paint.setTextSize(32);
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawText("Hello", 0, 50, paint);
|
||||
picture.endRecording();
|
||||
|
||||
ImageView iv1 = new ImageView(this);
|
||||
iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888));
|
||||
inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inner.addView(new View(this), new LayoutParams(50, 1));
|
||||
|
||||
ImageView iv2 = new ImageView(this);
|
||||
iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE));
|
||||
inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
layout.addView(inner,
|
||||
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
// For testing with a functor in the tree
|
||||
WebView wv = new WebView(this);
|
||||
wv.setWebViewClient(new WebViewClient());
|
||||
wv.setWebChromeClient(new WebChromeClient());
|
||||
wv.loadUrl("https://google.com");
|
||||
layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400));
|
||||
|
||||
SurfaceView mySurfaceView = new SurfaceView(this);
|
||||
layout.addView(mySurfaceView,
|
||||
new LayoutParams(LayoutParams.MATCH_PARENT, 600));
|
||||
|
||||
setContentView(layout);
|
||||
|
||||
mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
|
||||
private AutoCloseable mStopCapture;
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
final Random rand = new Random();
|
||||
mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView,
|
||||
mCaptureThread, (picture) -> {
|
||||
if (rand.nextInt(20) == 0) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
Canvas canvas = holder.lockCanvas();
|
||||
if (canvas == null) {
|
||||
return false;
|
||||
}
|
||||
canvas.drawPicture(picture);
|
||||
holder.unlockCanvasAndPost(canvas);
|
||||
picture.close();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (mStopCapture != null) {
|
||||
try {
|
||||
mStopCapture.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
mStopCapture = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ExecutorService mCaptureThread = Executors.newSingleThreadExecutor();
|
||||
ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
Picture deepCopy(Picture src) {
|
||||
try {
|
||||
PipedInputStream inputStream = new PipedInputStream();
|
||||
PipedOutputStream outputStream = new PipedOutputStream(inputStream);
|
||||
Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream));
|
||||
src.writeToStream(outputStream);
|
||||
outputStream.close();
|
||||
return future.get();
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user