mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
Adding support for continously capturing view hierarcy in Launcher
Bug: 238243939 Test: Verified data being captured and dumped Change-Id: Ibe069d39ccf728f7b953f85085e58976be6e05ac
This commit is contained in:
54
protos/view_capture.proto
Normal file
54
protos/view_capture.proto
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package com.android.launcher3.view;
|
||||
|
||||
option java_outer_classname = "ViewCaptureData";
|
||||
|
||||
message ExportedData {
|
||||
|
||||
repeated FrameData frameData = 1;
|
||||
}
|
||||
|
||||
message FrameData {
|
||||
optional int64 timestamp = 1;
|
||||
optional ViewNode node = 2;
|
||||
}
|
||||
|
||||
message ViewNode {
|
||||
optional string classname = 1;
|
||||
optional string id = 2;
|
||||
optional int32 left = 3;
|
||||
optional int32 top = 4;
|
||||
optional int32 width = 5;
|
||||
optional int32 height = 6;
|
||||
optional int32 scrollX = 7;
|
||||
optional int32 scrollY = 8;
|
||||
|
||||
optional float translationX = 9;
|
||||
optional float translationY = 10;
|
||||
optional float scaleX = 11 [default = 1];
|
||||
optional float scaleY = 12 [default = 1];
|
||||
optional float alpha = 13 [default = 1];
|
||||
|
||||
optional bool willNotDraw = 14;
|
||||
optional bool clipChildren = 15;
|
||||
optional int32 visibility = 16;
|
||||
|
||||
repeated ViewNode children = 17;
|
||||
}
|
||||
@@ -194,6 +194,7 @@ import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.launcher3.util.ViewCapture;
|
||||
import com.android.launcher3.util.ViewOnDrawExecutor;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
@@ -388,6 +389,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
private LauncherState mPrevLauncherState;
|
||||
|
||||
private StringCache mStringCache;
|
||||
private ViewCapture mViewCapture;
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.S)
|
||||
@@ -1478,6 +1480,14 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mOverlayManager.onAttachedToWindow();
|
||||
if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
|
||||
View root = getDragLayer().getRootView();
|
||||
if (mViewCapture != null) {
|
||||
root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
|
||||
}
|
||||
mViewCapture = new ViewCapture(root);
|
||||
root.getViewTreeObserver().addOnDrawListener(mViewCapture);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2997,6 +3007,10 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
|
||||
writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening());
|
||||
|
||||
if (mViewCapture != null) {
|
||||
writer.println(prefix + "\tmViewCapture: " + mViewCapture.dumpToString());
|
||||
}
|
||||
|
||||
// Extra logging for general debugging
|
||||
mDragLayer.dump(prefix, writer);
|
||||
mStateManager.dump(prefix, writer);
|
||||
|
||||
@@ -285,6 +285,9 @@ public final class FeatureFlags {
|
||||
"USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", false,
|
||||
"Use local overrides for search request timeout");
|
||||
|
||||
public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(
|
||||
"CONTINUOUS_VIEW_TREE_CAPTURE", false, "Capture View tree every frame");
|
||||
|
||||
public static void initialize(Context context) {
|
||||
synchronized (sDebugFlags) {
|
||||
for (DebugFlag flag : sDebugFlags) {
|
||||
|
||||
212
src/com/android/launcher3/util/ViewCapture.java
Normal file
212
src/com/android/launcher3/util/ViewCapture.java
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.launcher3.util;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver.OnDrawListener;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.view.ViewCaptureData.ExportedData;
|
||||
import com.android.launcher3.view.ViewCaptureData.FrameData;
|
||||
import com.android.launcher3.view.ViewCaptureData.ViewNode;
|
||||
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Utility class for capturing view data every frame
|
||||
*/
|
||||
public class ViewCapture implements OnDrawListener {
|
||||
|
||||
private static final String TAG = "ViewCapture";
|
||||
|
||||
private static final int MEMORY_SIZE = 2000;
|
||||
|
||||
private final View mRoot;
|
||||
private final long[] mFrameTimes = new long[MEMORY_SIZE];
|
||||
private final Node[] mNodes = new Node[MEMORY_SIZE];
|
||||
|
||||
private int mFrameIndex = -1;
|
||||
|
||||
/**
|
||||
* @param root the root view for the capture data
|
||||
*/
|
||||
public ViewCapture(View root) {
|
||||
mRoot = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw() {
|
||||
Trace.beginSection("view_capture");
|
||||
long now = SystemClock.elapsedRealtimeNanos();
|
||||
|
||||
mFrameIndex++;
|
||||
if (mFrameIndex >= MEMORY_SIZE) {
|
||||
mFrameIndex = 0;
|
||||
}
|
||||
mFrameTimes[mFrameIndex] = now;
|
||||
mNodes[mFrameIndex] = captureView(mRoot, mNodes[mFrameIndex]);
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a proto of all the data captured so far.
|
||||
*/
|
||||
public String dumpToString() {
|
||||
Handler handler = mRoot.getHandler();
|
||||
if (handler == null) {
|
||||
handler = Executors.MAIN_EXECUTOR.getHandler();
|
||||
}
|
||||
FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI);
|
||||
if (Looper.myLooper() == handler.getLooper()) {
|
||||
task.run();
|
||||
} else {
|
||||
handler.post(task);
|
||||
}
|
||||
try {
|
||||
return Base64.encodeToString(task.get().toByteArray(),
|
||||
Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error capturing proto", e);
|
||||
return "--error--";
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private ExportedData dumpToProtoUI() {
|
||||
ExportedData.Builder dataBuilder = ExportedData.newBuilder();
|
||||
Resources res = mRoot.getResources();
|
||||
|
||||
int size = (mNodes[MEMORY_SIZE - 1] == null) ? mFrameIndex + 1 : MEMORY_SIZE;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int index = (MEMORY_SIZE + mFrameIndex - i) % MEMORY_SIZE;
|
||||
dataBuilder.addFrameData(FrameData.newBuilder()
|
||||
.setNode(mNodes[index].toProto(res))
|
||||
.setTimestamp(mFrameTimes[index]));
|
||||
}
|
||||
return dataBuilder.build();
|
||||
}
|
||||
|
||||
private Node captureView(View view, Node recycle) {
|
||||
Node result = recycle == null ? new Node() : recycle;
|
||||
|
||||
result.clazz = view.getClass();
|
||||
result.hashCode = view.hashCode();
|
||||
result.id = view.getId();
|
||||
result.left = view.getLeft();
|
||||
result.top = view.getTop();
|
||||
result.right = view.getRight();
|
||||
result.bottom = view.getBottom();
|
||||
result.scrollX = view.getScrollX();
|
||||
result.scrollY = view.getScrollY();
|
||||
|
||||
result.translateX = view.getTranslationX();
|
||||
result.translateY = view.getTranslationY();
|
||||
result.scaleX = view.getScaleX();
|
||||
result.scaleY = view.getScaleY();
|
||||
result.alpha = view.getAlpha();
|
||||
|
||||
result.visibility = view.getVisibility();
|
||||
result.willNotDraw = view.willNotDraw();
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup parent = (ViewGroup) view;
|
||||
result.clipChildren = parent.getClipChildren();
|
||||
int childCount = parent.getChildCount();
|
||||
if (childCount == 0) {
|
||||
result.children = null;
|
||||
} else {
|
||||
result.children = captureView(parent.getChildAt(0), result.children);
|
||||
Node lastChild = result.children;
|
||||
for (int i = 1; i < childCount; i++) {
|
||||
lastChild.sibling = captureView(parent.getChildAt(i), lastChild.sibling);
|
||||
lastChild = lastChild.sibling;
|
||||
}
|
||||
lastChild.sibling = null;
|
||||
}
|
||||
} else {
|
||||
result.clipChildren = false;
|
||||
result.children = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
|
||||
// We store reference in memory to avoid generating and storing too many strings
|
||||
public Class clazz;
|
||||
public int hashCode;
|
||||
|
||||
public int id;
|
||||
public int left, top, right, bottom;
|
||||
public int scrollX, scrollY;
|
||||
|
||||
public float translateX, translateY;
|
||||
public float scaleX, scaleY;
|
||||
public float alpha;
|
||||
|
||||
public int visibility;
|
||||
public boolean willNotDraw;
|
||||
public boolean clipChildren;
|
||||
|
||||
public Node sibling;
|
||||
public Node children;
|
||||
|
||||
public ViewNode toProto(Resources res) {
|
||||
String resolvedId;
|
||||
if (id >= 0) {
|
||||
try {
|
||||
resolvedId = res.getResourceTypeName(id) + '/' + res.getResourceEntryName(id);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
resolvedId = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
|
||||
}
|
||||
} else {
|
||||
resolvedId = "NO_ID";
|
||||
}
|
||||
|
||||
ViewNode.Builder result = ViewNode.newBuilder()
|
||||
.setClassname(clazz.getName() + "@" + hashCode)
|
||||
.setId(resolvedId)
|
||||
.setLeft(left)
|
||||
.setTop(top)
|
||||
.setWidth(right - left)
|
||||
.setHeight(bottom - top)
|
||||
.setTranslationX(translateX)
|
||||
.setTranslationY(translateY)
|
||||
.setScaleX(scaleX)
|
||||
.setScaleY(scaleY)
|
||||
.setAlpha(alpha)
|
||||
.setVisibility(visibility)
|
||||
.setWillNotDraw(willNotDraw)
|
||||
.setClipChildren(clipChildren);
|
||||
Node child = children;
|
||||
while (child != null) {
|
||||
result.addChildren(child.toProto(res));
|
||||
child = child.sibling;
|
||||
}
|
||||
return result.build();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user