summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Peter Kalauskas <peskal@google.com> 2024-11-18 17:22:29 -0800
committer Peter Kalauskas <peskal@google.com> 2024-11-25 14:38:49 -0800
commitb879d0d022d38b8fa08ce832cbd2fb79c6fd76e0 (patch)
tree97cd36e8ea01ea835e505dfd4b8b0397eec7d354
parent025f8b58a19ed417351a2fdf01ec8f0e57e90ac8 (diff)
New window for drawing debug overlay
Instead of drawing the debug overlay inside the window of one of the tracked CUIs, create a new window solely for the debug overlay. This ensures that the debug overlay is always legible. This will affect performance somewhat, but the debug overlay should not be used when testing performance. The debug overlay is also disabled on non-debuggable builds. Test: atest InteractionJankMonitorTest FrameTrackerTest Flag: EXEMPT Debug-only change Bug: 238923086 Change-Id: I952997026aa7d241b4cdfa53a86e02087d663c93
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java8
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java39
-rw-r--r--core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java314
3 files changed, 168 insertions, 193 deletions
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 2834e6883316..454323b60333 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -685,14 +685,6 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
}
}
- ThreadedRendererWrapper getThreadedRenderer() {
- return mRendererWrapper;
- }
-
- ViewRootWrapper getViewRoot() {
- return mViewRoot;
- }
-
private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
&& missedFramesCount >= mTraceThresholdMissedFrames;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index ef08e49ce6d9..4ee6c65b08f8 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -31,6 +31,7 @@ import android.annotation.RequiresPermission;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
import android.app.ActivityThread;
+import android.app.Application;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
@@ -184,10 +185,12 @@ public class InteractionJankMonitor {
@GuardedBy("mLock")
private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
private final Handler mWorker;
+ private final Application mCurrentApplication;
private final DisplayResolutionTracker mDisplayResolutionTracker;
private final Object mLock = new Object();
private @ColorInt int mDebugBgColor = Color.CYAN;
private double mDebugYOffset = 0.1;
+ @GuardedBy("mLock")
private InteractionMonitorDebugOverlay mDebugOverlay;
private volatile boolean mEnabled = DEFAULT_ENABLED;
@@ -216,13 +219,15 @@ public class InteractionJankMonitor {
mWorker = worker.getThreadHandler();
mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
- final Context context = ActivityThread.currentApplication();
- if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+ mCurrentApplication = ActivityThread.currentApplication();
+ if (mCurrentApplication == null || mCurrentApplication.checkCallingOrSelfPermission(
+ READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
+ " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+ ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+ ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
- + ", package=" + (context == null ? "null" : context.getPackageName()));
+ + ", package=" + (mCurrentApplication == null ? "null"
+ : mCurrentApplication.getPackageName()));
return;
}
@@ -234,8 +239,8 @@ public class InteractionJankMonitor {
new HandlerExecutor(mWorker), this::updateProperties);
} catch (SecurityException ex) {
Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
- + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
- + ", package=" + context.getPackageName());
+ + mCurrentApplication.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ + ", package=" + mCurrentApplication.getPackageName());
}
});
}
@@ -534,7 +539,7 @@ public class InteractionJankMonitor {
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerAdded(cuj, tracker);
+ mDebugOverlay.onTrackerAdded(cuj);
}
return tracker;
@@ -569,7 +574,7 @@ public class InteractionJankMonitor {
running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
mRunningTrackers.remove(cuj);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
+ mDebugOverlay.onTrackerRemoved(cuj, reason);
}
return false;
}
@@ -592,14 +597,18 @@ public class InteractionJankMonitor {
mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
// Never allow the debug overlay to be used on user builds
- boolean debugOverlayEnabled = Build.IS_DEBUGGABLE
- && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED);
- if (debugOverlayEnabled && mDebugOverlay == null) {
- mDebugOverlay = new InteractionMonitorDebugOverlay(
- mLock, mDebugBgColor, mDebugYOffset);
- } else if (!debugOverlayEnabled && mDebugOverlay != null) {
- mDebugOverlay.dispose();
- mDebugOverlay = null;
+ if (Build.IS_USER) break;
+ boolean debugOverlayEnabled = properties.getBoolean(property,
+ DEFAULT_DEBUG_OVERLAY_ENABLED);
+ synchronized (mLock) {
+ if (debugOverlayEnabled && mDebugOverlay == null) {
+ // Use the worker thread as the UI thread for the debug overlay:
+ mDebugOverlay = new InteractionMonitorDebugOverlay(
+ mCurrentApplication, mWorker, mDebugBgColor, mDebugYOffset);
+ } else if (!debugOverlayEnabled && mDebugOverlay != null) {
+ mDebugOverlay.dispose();
+ mDebugOverlay = null;
+ }
}
}
default -> Log.w(TAG, "Got a change event for an unknown property: "
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index d9cac12c3372..009837b686c3 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -16,24 +16,36 @@
package com.android.internal.jank;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Gravity.CENTER;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
+import android.annotation.AnyThread;
import android.annotation.ColorInt;
+import android.annotation.NonNull;
import android.annotation.UiThread;
-import android.app.ActivityThread;
+import android.app.Application;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.RecordingCanvas;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Trace;
+import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.view.WindowCallbacks;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.jank.FrameTracker.Reasons;
/**
@@ -50,210 +62,171 @@ import com.android.internal.jank.FrameTracker.Reasons;
* <li> Grey text indicates the CUJ ended normally and is no longer running
* <li> Red text with a strikethrough indicates the CUJ was canceled or ended abnormally
* </ul>
+ *
* @hide
*/
-class InteractionMonitorDebugOverlay implements WindowCallbacks {
+class InteractionMonitorDebugOverlay {
private static final String TAG = "InteractionMonitorDebug";
private static final int REASON_STILL_RUNNING = -1000;
- private final Object mLock;
// Sparse array where the key in the CUJ and the value is the session status, or null if
// it's currently running
- @GuardedBy("mLock")
+ private final Application mCurrentApplication;
+ private final Handler mUiThread;
+ private final DebugOverlayView mDebugOverlayView;
+ private final WindowManager mWindowManager;
private final SparseIntArray mRunningCujs = new SparseIntArray();
- private Handler mHandler = null;
- private FrameTracker.ViewRootWrapper mViewRoot = null;
- private final Paint mDebugPaint;
- private final Paint.FontMetrics mDebugFontMetrics;
- // Used to display the overlay in a different color and position for different processes.
- // Otherwise, two overlays will overlap and be difficult to read.
- private final int mBgColor;
- private final double mYOffset;
- private final String mPackageName;
- private static final String TRACK_NAME = "InteractionJankMonitor";
- InteractionMonitorDebugOverlay(Object lock, @ColorInt int bgColor, double yOffset) {
- mLock = lock;
- mBgColor = bgColor;
- mYOffset = yOffset;
- mDebugPaint = new Paint();
- mDebugPaint.setAntiAlias(false);
- mDebugFontMetrics = new Paint.FontMetrics();
- final Context context = ActivityThread.currentApplication();
- mPackageName = context == null ? "null" : context.getPackageName();
- }
+ InteractionMonitorDebugOverlay(@NonNull Application currentApplication,
+ @NonNull @UiThread Handler uiThread, @ColorInt int bgColor, double yOffset) {
+ mCurrentApplication = currentApplication;
+ mUiThread = uiThread;
+ final Display display = mCurrentApplication.getSystemService(
+ DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ final Context windowContext = mCurrentApplication.createDisplayContext(
+ display).createWindowContext(TYPE_SYSTEM_OVERLAY, null /* options */);
+ mWindowManager = windowContext.getSystemService(WindowManager.class);
- @UiThread
- void dispose() {
- if (mViewRoot != null && mHandler != null) {
- mHandler.runWithScissors(() -> mViewRoot.removeWindowCallbacks(this),
- InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
- forceRedraw();
- }
- mHandler = null;
- mViewRoot = null;
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
- }
+ final Rect size = mWindowManager.getCurrentWindowMetrics().getBounds();
- @UiThread
- private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) {
- FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot();
- if (mViewRoot == null && viewRoot != null) {
- // Add a trace marker so we can identify traces that were captured while the debug
- // overlay was enabled. Traces that use the debug overlay should NOT be used for
- // performance analysis.
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
- mHandler = tracker.mConfig.getHandler();
- mViewRoot = viewRoot;
- mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
- InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
- forceRedraw();
- return true;
- }
- return false;
- }
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
- @GuardedBy("mLock")
- private float getWidthOfLongestCujName(int cujFontSize) {
- mDebugPaint.setTextSize(cujFontSize);
- float maxLength = 0;
- for (int i = 0; i < mRunningCujs.size(); i++) {
- String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
- float textLength = mDebugPaint.measureText(cujName);
- if (textLength > maxLength) {
- maxLength = textLength;
- }
- }
- return maxLength;
- }
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.setFitInsetsTypes(0 /* types */);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
- private float getTextHeight(int textSize) {
- mDebugPaint.setTextSize(textSize);
- mDebugPaint.getFontMetrics(mDebugFontMetrics);
- return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
- }
+ lp.width = size.width();
+ lp.height = size.height();
+ lp.gravity = CENTER;
+ lp.setTitle("InteractionMonitorDebugOverlay");
- private int dipToPx(int dip) {
- if (mViewRoot != null) {
- return mViewRoot.dipToPx(dip);
- } else {
- return dip;
+ if (!mUiThread.getLooper().isCurrentThread()) {
+ Log.e(TAG, "InteractionMonitorDebugOverlay must be constructed on "
+ + "InteractionJankMonitor's worker thread");
}
+ mDebugOverlayView = new DebugOverlayView(mCurrentApplication, bgColor, yOffset);
+ mWindowManager.addView(mDebugOverlayView, lp);
}
- @UiThread
- private void forceRedraw() {
- if (mViewRoot != null && mHandler != null) {
- mHandler.runWithScissors(() -> {
- mViewRoot.requestInvalidateRootRenderNode();
- mViewRoot.getView().invalidate();
- }, InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
- }
+ @AnyThread
+ void onTrackerAdded(@Cuj.CujType int addedCuj) {
+ mUiThread.post(() -> {
+ String cujName = Cuj.getNameOfCuj(addedCuj);
+ Log.i(TAG, cujName + " started");
+ // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
+ // is still running
+ mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
+ mDebugOverlayView.setVisibility(VISIBLE);
+ mDebugOverlayView.invalidate();
+ });
}
- @UiThread
- void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason,
- SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
- synchronized (mLock) {
+ @AnyThread
+ void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason) {
+ mUiThread.post(() -> {
mRunningCujs.put(removedCuj, reason);
- boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
- if (isLoggable) {
- String cujName = Cuj.getNameOfCuj(removedCuj);
- Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
- }
+ String cujName = Cuj.getNameOfCuj(removedCuj);
+ Log.i(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
// If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
- if (isLoggable) Log.d(TAG, "All CUJs ended");
+ Log.i(TAG, "All CUJs ended");
mRunningCujs.clear();
- dispose();
- } else {
- boolean needsNewViewRoot = true;
- if (mViewRoot != null) {
- // Check to see if this viewroot is still associated with one of the running
- // trackers
- for (int i = 0; i < runningTrackers.size(); i++) {
- if (mViewRoot.equals(
- runningTrackers.valueAt(i).mTracker.getViewRoot())) {
- needsNewViewRoot = false;
- break;
- }
- }
- }
- if (needsNewViewRoot) {
- dispose();
- for (int i = 0; i < runningTrackers.size(); i++) {
- if (attachViewRootIfNeeded(runningTrackers.valueAt(i))) {
- break;
- }
- }
- } else {
- forceRedraw();
- }
+ mDebugOverlayView.setVisibility(INVISIBLE);
}
- }
+ mDebugOverlayView.invalidate();
+ });
}
- @UiThread
- void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- String cujName = Cuj.getNameOfCuj(addedCuj);
- Log.d(TAG, cujName + " started");
- }
- synchronized (mLock) {
- // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
- // is still running
- mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
- attachViewRootIfNeeded(tracker);
- forceRedraw();
- }
+ @AnyThread
+ void dispose() {
+ mUiThread.post(() -> {
+ mWindowManager.removeView(mDebugOverlayView);
+ });
}
- @Override
- public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen,
- Rect systemInsets, Rect stableInsets) {
- }
+ @UiThread
+ private class DebugOverlayView extends View {
+ private static final String TRACK_NAME = "InteractionJankMonitor";
- @Override
- public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
- Rect systemInsets, Rect stableInsets) {
- }
+ // Used to display the overlay in a different color and position for different processes.
+ // Otherwise, two overlays will overlap and be difficult to read.
+ private final int mBgColor;
+ private final double mYOffset;
- @Override
- public void onWindowDragResizeEnd() {
- }
+ private final float mDensity;
+ private final Paint mDebugPaint;
+ private final Paint.FontMetrics mDebugFontMetrics;
+ private final String mPackageName;
- @Override
- public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
- return false;
- }
+ private DebugOverlayView(Context context, @ColorInt int bgColor, double yOffset) {
+ super(context);
+ setVisibility(INVISIBLE);
+ mBgColor = bgColor;
+ mYOffset = yOffset;
+ final DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
+ mDensity = displayMetrics.density;
+ mDebugPaint = new Paint();
+ mDebugPaint.setAntiAlias(false);
+ mDebugFontMetrics = new Paint.FontMetrics();
+ mPackageName = mCurrentApplication.getPackageName();
+ }
- @Override
- public void onRequestDraw(boolean reportNextDraw) {
- }
+ private int dipToPx(int dip) {
+ return (int) (mDensity * dip + 0.5f);
+ }
+
+ private float getTextHeight(int textSize) {
+ mDebugPaint.setTextSize(textSize);
+ mDebugPaint.getFontMetrics(mDebugFontMetrics);
+ return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
+ }
- @Override
- public void onPostDraw(RecordingCanvas canvas) {
- final int padding = dipToPx(5);
- final int h = canvas.getHeight();
- final int w = canvas.getWidth();
- // Draw sysui CUjs near the bottom of the screen so they don't overlap with the shade,
- // and draw launcher CUJs near the top of the screen so they don't overlap with gestures
- final int dy = (int) (h * mYOffset);
- int packageNameFontSize = dipToPx(12);
- int cujFontSize = dipToPx(18);
- final float cujNameTextHeight = getTextHeight(cujFontSize);
- final float packageNameTextHeight = getTextHeight(packageNameFontSize);
+ private float getWidthOfLongestCujName(int cujFontSize) {
+ mDebugPaint.setTextSize(cujFontSize);
+ float maxLength = 0;
+ for (int i = 0; i < mRunningCujs.size(); i++) {
+ String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
+ float textLength = mDebugPaint.measureText(cujName);
+ if (textLength > maxLength) {
+ maxLength = textLength;
+ }
+ }
+ return maxLength;
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Add a trace marker so we can identify traces that were captured while the debug
+ // overlay was enabled. Traces that use the debug overlay should NOT be used for
+ // performance analysis.
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
- synchronized (mLock) {
+ final int padding = dipToPx(5);
+ final int h = getHeight();
+ final int w = getWidth();
+ final int dy = (int) (h * mYOffset);
+ int packageNameFontSize = dipToPx(12);
+ int cujFontSize = dipToPx(18);
+ final float cujNameTextHeight = getTextHeight(cujFontSize);
+ final float packageNameTextHeight = getTextHeight(packageNameFontSize);
float maxLength = getWidthOfLongestCujName(cujFontSize);
final int dx = (int) ((w - maxLength) / 2f);
canvas.translate(dx, dy);
// Draw background rectangle for displaying the text showing the CUJ name
mDebugPaint.setColor(mBgColor);
- canvas.drawRect(
- -padding * 2, // more padding on top so we can draw the package name
- -padding,
- padding * 2 + maxLength,
+ canvas.drawRect(-padding * 2, // more padding on top so we can draw the package name
+ -padding, padding * 2 + maxLength,
padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
mDebugPaint);
mDebugPaint.setTextSize(packageNameFontSize);
@@ -280,6 +253,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
canvas.translate(0, cujNameTextHeight);
canvas.drawText(cujName, 0, 0, mDebugPaint);
}
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
}
}
}