summaryrefslogtreecommitdiff
path: root/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
diff options
context:
space:
mode:
Diffstat (limited to 'viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java')
-rw-r--r--viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java171
1 files changed, 77 insertions, 94 deletions
diff --git a/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java b/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
index 70c58cb..fcd7ad8 100644
--- a/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
+++ b/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
@@ -35,18 +35,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
-import android.os.Process;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
-import com.android.app.viewcapture.data.nano.ExportedData;
-import com.android.app.viewcapture.data.nano.FrameData;
-import com.android.app.viewcapture.data.nano.ViewNode;
-
-import com.google.protobuf.nano.MessageNano;
+import com.android.app.viewcapture.data.ExportedData;
+import com.android.app.viewcapture.data.FrameData;
+import com.android.app.viewcapture.data.ViewNode;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -54,9 +50,8 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.function.Consumer;
import java.util.zip.GZIPOutputStream;
@@ -64,7 +59,7 @@ import java.util.zip.GZIPOutputStream;
/**
* Utility class for capturing view data every frame
*/
-public class ViewCapture {
+public abstract class ViewCapture {
private static final String TAG = "ViewCapture";
@@ -74,55 +69,31 @@ public class ViewCapture {
// Number of frames to keep in memory
private final int mMemorySize;
- private static final int DEFAULT_MEMORY_SIZE = 2000;
+ protected static final int DEFAULT_MEMORY_SIZE = 2000;
// Initial size of the reference pool. This is at least be 5 * total number of views in
// Launcher. This allows the first free frames avoid object allocation during view capture.
- private static final int DEFAULT_INIT_POOL_SIZE = 300;
+ protected static final int DEFAULT_INIT_POOL_SIZE = 300;
- private static ViewCapture INSTANCE;
public static final LooperExecutor MAIN_EXECUTOR = new LooperExecutor(Looper.getMainLooper());
- public static ViewCapture getInstance() {
- return getInstance(true, DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE);
- }
-
- @VisibleForTesting
- public static ViewCapture getInstance(boolean offloadToBackgroundThread, int memorySize,
- int initPoolSize) {
- if (INSTANCE == null) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- INSTANCE = new ViewCapture(offloadToBackgroundThread, memorySize, initPoolSize);
- } else {
- try {
- return MAIN_EXECUTOR.submit(() ->
- getInstance(offloadToBackgroundThread, memorySize, initPoolSize)).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }
- return INSTANCE;
- }
-
private final List<WindowListener> mListeners = new ArrayList<>();
- private final Executor mExecutor;
+ protected final Executor mBgExecutor;
+ private final Choreographer mChoreographer;
// Pool used for capturing view tree on the UI thread.
private ViewRef mPool = new ViewRef();
+ private boolean mIsEnabled = true;
- private ViewCapture(boolean offloadToBackgroundThread, int memorySize, int initPoolSize) {
+ protected ViewCapture(int memorySize, int initPoolSize, Choreographer choreographer,
+ Executor bgExecutor) {
mMemorySize = memorySize;
- if (offloadToBackgroundThread) {
- mExecutor = createAndStartNewLooperExecutor("ViewCapture",
- Process.THREAD_PRIORITY_FOREGROUND);
- } else {
- mExecutor = MAIN_EXECUTOR;
- }
- mExecutor.execute(() -> initPool(initPoolSize));
+ mChoreographer = choreographer;
+ mBgExecutor = bgExecutor;
+ mBgExecutor.execute(() -> initPool(initPoolSize));
}
- private static LooperExecutor createAndStartNewLooperExecutor(String name, int priority) {
+ public static LooperExecutor createAndStartNewLooperExecutor(String name, int priority) {
HandlerThread thread = new HandlerThread(name, priority);
thread.start();
return new LooperExecutor(thread.getLooper());
@@ -158,22 +129,34 @@ public class ViewCapture {
}
/**
- * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
+ * Attaches the ViewCapture to the provided window and returns a handle to detach the listener.
+ * Verifies that ViewCapture is enabled before actually attaching an onDrawListener.
*/
public SafeCloseable startCapture(View view, String name) {
WindowListener listener = new WindowListener(view, name);
- mExecutor.execute(() -> MAIN_EXECUTOR.execute(listener::attachToRoot));
+ if (mIsEnabled) MAIN_EXECUTOR.execute(listener::attachToRoot);
mListeners.add(listener);
return () -> {
mListeners.remove(listener);
- listener.destroy();
+ listener.detachFromRoot();
};
}
+ @UiThread
+ protected void enableOrDisableWindowListeners(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ mListeners.forEach(WindowListener::detachFromRoot);
+ if (mIsEnabled) mListeners.forEach(WindowListener::attachToRoot);
+ }
+
+
/**
* Dumps all the active view captures
*/
public void dump(PrintWriter writer, FileDescriptor out, Context context) {
+ if (!mIsEnabled) {
+ return;
+ }
ViewIdProvider idProvider = new ViewIdProvider(context.getResources());
// Collect all the tasks first so that all the tasks are posted on the executor
@@ -181,7 +164,7 @@ public class ViewCapture {
.map(l -> {
FutureTask<ExportedData> task =
new FutureTask<ExportedData>(() -> l.dumpToProto(idProvider));
- mExecutor.execute(task);
+ mBgExecutor.execute(task);
return Pair.create(l.name, task);
})
.collect(toList());
@@ -196,7 +179,7 @@ public class ViewCapture {
ExportedData data = pair.second.get();
OutputStream encodedOS = new GZIPOutputStream(new Base64OutputStream(os,
Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP));
- encodedOS.write(MessageNano.toByteArray(data));
+ data.writeTo(encodedOS);
encodedOS.close();
os.flush();
} catch (Exception e) {
@@ -216,7 +199,7 @@ public class ViewCapture {
.map(l -> {
FutureTask<ExportedData> task =
new FutureTask<ExportedData>(() -> l.dumpToProto(idProvider));
- mExecutor.execute(task);
+ mBgExecutor.execute(task);
return task;
})
.findFirst();
@@ -272,16 +255,10 @@ public class ViewCapture {
private final long[] mFrameTimesNanosBg = new long[mMemorySize];
private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[mMemorySize];
- private boolean mDestroyed = false;
+ private boolean mIsActive = true;
private final Consumer<ViewRef> mCaptureCallback = this::captureViewPropertiesBg;
- private Choreographer mChoreographer;
WindowListener(View view, String name) {
- try {
- mChoreographer = MAIN_EXECUTOR.submit(Choreographer::getInstance).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
mRoot = view;
this.name = name;
}
@@ -300,7 +277,7 @@ public class ViewCapture {
if (captured != null) {
captured.callback = mCaptureCallback;
captured.choreographerTimeNanos = mChoreographer.getFrameTimeNanos();
- mExecutor.execute(captured);
+ mBgExecutor.execute(captured);
}
mIsFirstFrame = false;
Trace.endSection();
@@ -392,14 +369,15 @@ public class ViewCapture {
}
void attachToRoot() {
+ mIsActive = true;
if (mRoot.isAttachedToWindow()) {
- mRoot.getViewTreeObserver().addOnDrawListener(this);
+ safelyEnableOnDrawListener();
} else {
mRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
- if (!mDestroyed) {
- mRoot.getViewTreeObserver().addOnDrawListener(WindowListener.this);
+ if (mIsActive) {
+ safelyEnableOnDrawListener();
}
mRoot.removeOnAttachStateChangeListener(this);
}
@@ -411,29 +389,34 @@ public class ViewCapture {
}
}
- void destroy() {
+ void detachFromRoot() {
+ mIsActive = false;
mRoot.getViewTreeObserver().removeOnDrawListener(this);
- mDestroyed = true;
+ }
+
+ private void safelyEnableOnDrawListener() {
+ mRoot.getViewTreeObserver().removeOnDrawListener(this);
+ mRoot.getViewTreeObserver().addOnDrawListener(this);
}
@WorkerThread
private ExportedData dumpToProto(ViewIdProvider idProvider) {
int size = (mNodesBg[mMemorySize - 1] == null) ? mFrameIndexBg + 1 : mMemorySize;
- ExportedData exportedData = new ExportedData();
- exportedData.frameData = new FrameData[size];
+ ExportedData.Builder exportedDataBuilder = ExportedData.newBuilder();
ArrayList<Class> classList = new ArrayList<>();
for (int i = size - 1; i >= 0; i--) {
int index = (mMemorySize + mFrameIndexBg - i) % mMemorySize;
- ViewNode node = new ViewNode();
- mNodesBg[index].toProto(idProvider, classList, node);
- FrameData frameData = new FrameData();
- frameData.node = node;
- frameData.timestamp = mFrameTimesNanosBg[index];
- exportedData.frameData[size - i - 1] = frameData;
+ ViewNode.Builder nodeBuilder = ViewNode.newBuilder();
+ mNodesBg[index].toProto(idProvider, classList, nodeBuilder);
+ FrameData.Builder frameDataBuilder = FrameData.newBuilder()
+ .setNode(nodeBuilder)
+ .setTimestamp(mFrameTimesNanosBg[index]);
+ exportedDataBuilder.addFrameData(frameDataBuilder);
}
- exportedData.classname = classList.stream().map(Class::getName).toArray(String[]::new);
- return exportedData;
+ return exportedDataBuilder
+ .addAllClassname(classList.stream().map(Class::getName).collect(toList()))
+ .build();
}
private ViewRef captureViewTree(View view, ViewRef start) {
@@ -518,35 +501,35 @@ public class ViewCapture {
* at the end of the iteration.
*/
public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList<Class> classList,
- ViewNode viewNode) {
+ ViewNode.Builder viewNode) {
int classnameIndex = classList.indexOf(clazz);
if (classnameIndex < 0) {
classnameIndex = classList.size();
classList.add(clazz);
}
- viewNode.classnameIndex = classnameIndex;
- viewNode.hashcode = hashCode;
- viewNode.id = idProvider.getName(id);
- viewNode.left = left;
- viewNode.top = top;
- viewNode.width = right - left;
- viewNode.height = bottom - top;
- viewNode.translationX = translateX;
- viewNode.translationY = translateY;
- viewNode.scaleX = scaleX;
- viewNode.scaleY = scaleY;
- viewNode.alpha = alpha;
- viewNode.visibility = visibility;
- viewNode.willNotDraw = willNotDraw;
- viewNode.elevation = elevation;
- viewNode.clipChildren = clipChildren;
+
+ viewNode.setClassnameIndex(classnameIndex)
+ .setHashcode(hashCode)
+ .setId(idProvider.getName(id))
+ .setLeft(left)
+ .setTop(top)
+ .setWidth(right - left)
+ .setHeight(bottom - top)
+ .setTranslationX(translateX)
+ .setTranslationY(translateY)
+ .setScaleX(scaleX)
+ .setScaleY(scaleY)
+ .setAlpha(alpha)
+ .setVisibility(visibility)
+ .setWillNotDraw(willNotDraw)
+ .setElevation(elevation)
+ .setClipChildren(clipChildren);
ViewPropertyRef result = next;
- viewNode.children = new ViewNode[childCount];
for (int i = 0; (i < childCount) && (result != null); i++) {
- ViewNode childViewNode = new ViewNode();
+ ViewNode.Builder childViewNode = ViewNode.newBuilder();
result = result.toProto(idProvider, classList, childViewNode);
- viewNode.children[i] = childViewNode;
+ viewNode.addChildren(childViewNode);
}
return result;
}