diff options
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); } } } |