summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 1e211251fce3..cbc20dcb8fde 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());
}
});
}
@@ -538,7 +543,7 @@ public class InteractionJankMonitor {
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerAdded(cuj, tracker);
+ mDebugOverlay.onTrackerAdded(cuj);
}
return tracker;
@@ -573,7 +578,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;
}
@@ -596,14 +601,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);
}
}
}