Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a68ad848 authored by Peter Kalauskas's avatar Peter Kalauskas Committed by Automerger Merge Worker
Browse files

Merge "New jank flag for showing CUJ names on screen" into udc-dev am: d57e2fd6 am: 77364ea6

parents e8a37062 77364ea6
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.graphics.HardwareRendererObserver;
import android.os.Handler;
import android.os.Trace;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -43,7 +44,9 @@ import android.view.FrameMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControl.JankData.JankType;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowCallbacks;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -686,6 +689,14 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
        }
    }

    ThreadedRendererWrapper getThreadedRenderer() {
        return mRendererWrapper;
    }

    ViewRootWrapper getViewRoot() {
        return mViewRoot;
    }

    private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
        boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
                && missedFramesCount >= mTraceThresholdMissedFrames;
@@ -798,6 +809,28 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
        public SurfaceControl getSurfaceControl() {
            return mViewRoot.getSurfaceControl();
        }

        void requestInvalidateRootRenderNode() {
            mViewRoot.requestInvalidateRootRenderNode();
        }

        void addWindowCallbacks(WindowCallbacks windowCallbacks) {
            mViewRoot.addWindowCallbacks(windowCallbacks);
        }

        void removeWindowCallbacks(WindowCallbacks windowCallbacks) {
            mViewRoot.removeWindowCallbacks(windowCallbacks);
        }

        View getView() {
            return mViewRoot.getView();
        }

        int dipToPx(int dip) {
            final DisplayMetrics displayMetrics =
                    mViewRoot.mContext.getResources().getDisplayMetrics();
            return (int) (displayMetrics.density * dip + 0.5f);
        }
    }

    public static class SurfaceControlWrapper {
+76 −4
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;

import android.Manifest;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -104,6 +105,7 @@ import android.annotation.UiThread;
import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -143,6 +145,14 @@ import java.util.concurrent.TimeUnit;
 * adb shell device_config put interaction_jank_monitor enabled true
 * adb shell device_config put interaction_jank_monitor sampling_interval 1
 *
 * On debuggable builds, an overlay can be used to display the name of the
 * currently running cuj using:
 *
 * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
 *
 * NOTE: The overlay will interfere with metrics, so it should only be used
 * for understanding which UI events correspeond to which CUJs.
 *
 * @hide
 */
public class InteractionJankMonitor {
@@ -159,6 +169,7 @@ public class InteractionJankMonitor {
            "trace_threshold_missed_frames";
    private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
            "trace_threshold_frame_time_millis";
    private static final String SETTINGS_DEBUG_OVERLAY_ENABLED_KEY = "debug_overlay_enabled";
    /** Default to being enabled on debug builds. */
    private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
    /** Default to collecting data for all CUJs. */
@@ -166,6 +177,7 @@ public class InteractionJankMonitor {
    /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
    private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
    private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
    private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;

    @VisibleForTesting
    public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
@@ -343,6 +355,9 @@ public class InteractionJankMonitor {
    private final HandlerThread mWorker;
    private final DisplayResolutionTracker mDisplayResolutionTracker;
    private final Object mLock = new Object();
    private @ColorInt int mDebugBgColor = Color.CYAN;
    private double mDebugYOffset = 0.1;
    private InteractionMonitorDebugOverlay mDebugOverlay;

    private volatile boolean mEnabled = DEFAULT_ENABLED;
    private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
@@ -533,7 +548,7 @@ public class InteractionJankMonitor {
        if (needRemoveTasks(action, session)) {
            getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
                removeTimeout(session.getCuj());
                removeTracker(session.getCuj());
                removeTracker(session.getCuj(), session.getReason());
            }, EXECUTOR_TASK_TIMEOUT);
        }
    }
@@ -699,7 +714,7 @@ public class InteractionJankMonitor {
        if (tracker == null) return false;
        // if the end call doesn't return true, another thread is handling end of the cuj.
        if (tracker.end(REASON_END_NORMAL)) {
            removeTracker(cujType);
            removeTracker(cujType, REASON_END_NORMAL);
        }
        return true;
    }
@@ -750,7 +765,7 @@ public class InteractionJankMonitor {
        if (tracker == null) return false;
        // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
        if (tracker.cancel(reason)) {
            removeTracker(cujType);
            removeTracker(cujType, reason);
        }
        return true;
    }
@@ -758,6 +773,13 @@ public class InteractionJankMonitor {
    private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
        synchronized (mLock) {
            mRunningTrackers.put(cuj, tracker);
            if (mDebugOverlay != null) {
                mDebugOverlay.onTrackerAdded(cuj, tracker.getViewRoot());
            }
            if (DEBUG) {
                Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
            }
        }
    }

@@ -767,9 +789,16 @@ public class InteractionJankMonitor {
        }
    }

    private void removeTracker(@CujType int cuj) {
    private void removeTracker(@CujType int cuj, int reason) {
        synchronized (mLock) {
            mRunningTrackers.remove(cuj);
            if (mDebugOverlay != null) {
                mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
            }
            if (DEBUG) {
                Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj)
                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
            }
        }
    }

@@ -782,6 +811,16 @@ public class InteractionJankMonitor {
        mTraceThresholdFrameTimeMillis = properties.getInt(
                SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
                DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
        // Never allow the debug overlay to be used on user builds
        boolean debugOverlayEnabled = Build.IS_DEBUGGABLE && properties.getBoolean(
                SETTINGS_DEBUG_OVERLAY_ENABLED_KEY,
                DEFAULT_DEBUG_OVERLAY_ENABLED);
        if (debugOverlayEnabled && mDebugOverlay == null) {
            mDebugOverlay = new InteractionMonitorDebugOverlay(mDebugBgColor, mDebugYOffset);
        } else if (!debugOverlayEnabled && mDebugOverlay != null) {
            mDebugOverlay.dispose();
            mDebugOverlay = null;
        }
        // The memory visibility is powered by the volatile field, mEnabled.
        mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
    }
@@ -821,6 +860,39 @@ public class InteractionJankMonitor {
        return interactionType - 1;
    }

    /**
     * Configures the debug overlay used for displaying interaction names on the screen while they
     * occur.
     *
     * @param bgColor the background color of the box used to display the CUJ names
     * @param yOffset number between 0 and 1 to indicate where the top of the box should be relative
     *                to the height of the screen
     */
    public void configDebugOverlay(@ColorInt int bgColor, double yOffset) {
        mDebugBgColor = bgColor;
        mDebugYOffset = yOffset;
    }

    /**
     * A helper method for getting a string representation of all running CUJs. For example,
     * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)"
     */
    private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) {
        if (!DEBUG) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        for (int i = 0; i < trackers.size(); i++) {
            sb.append(getNameOfCuj(trackers.keyAt(i)));
            if (i < trackers.size() - 1) {
                sb.append(", ");
            }
        }
        sb.append(')');
        return sb.toString();
    }

    /**
     * A helper method to translate CUJ type to CUJ name.
     *
+240 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.internal.jank;

import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;

import android.annotation.ColorInt;
import android.app.ActivityThread;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.os.Trace;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.WindowCallbacks;

import com.android.internal.jank.FrameTracker.Reasons;
import com.android.internal.jank.InteractionJankMonitor.CujType;

/**
 * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
 * associated with one of the CUJs being tracked. There's no guarantee which window it will
 * draw to. NOTE: sometimes the CUJ names will remain displayed on the screen longer than they
 * are actually running.
 * <p>
 * CUJ names will be drawn as follows:
 * <ul>
 * <li> Normal text indicates the CUJ is currently running
 * <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>
 */
class InteractionMonitorDebugOverlay implements WindowCallbacks {
    private static final int REASON_STILL_RUNNING = -1000;
    // Sparse array where the key in the CUJ and the value is the session status, or null if
    // it's currently running
    private final SparseIntArray mRunningCujs = new SparseIntArray();
    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;

    InteractionMonitorDebugOverlay(@ColorInt int bgColor, double yOffset) {
        mBgColor = bgColor;
        mYOffset = yOffset;
        mDebugPaint = new Paint();
        mDebugPaint.setAntiAlias(false);
        mDebugFontMetrics = new Paint.FontMetrics();
        final Context context = ActivityThread.currentApplication();
        mPackageName = context.getPackageName();
    }

    void dispose() {
        if (mViewRoot != null) {
            mViewRoot.removeWindowCallbacks(this);
            forceRedraw();
        }
        mViewRoot = null;
    }

    private boolean attachViewRootIfNeeded(FrameTracker.ViewRootWrapper viewRoot) {
        if (mViewRoot == null && viewRoot != null) {
            mViewRoot = viewRoot;
            viewRoot.addWindowCallbacks(this);
            forceRedraw();
            return true;
        }
        return false;
    }

    private float getWidthOfLongestCujName(int cujFontSize) {
        mDebugPaint.setTextSize(cujFontSize);
        float maxLength = 0;
        for (int i = 0; i < mRunningCujs.size(); i++) {
            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
            float textLength = mDebugPaint.measureText(cujName);
            if (textLength > maxLength) {
                maxLength = textLength;
            }
        }
        return maxLength;
    }

    private float getTextHeight(int textSize) {
        mDebugPaint.setTextSize(textSize);
        mDebugPaint.getFontMetrics(mDebugFontMetrics);
        return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
    }

    private int dipToPx(int dip) {
        if (mViewRoot != null) {
            return mViewRoot.dipToPx(dip);
        } else {
            return dip;
        }
    }

    private void forceRedraw() {
        if (mViewRoot != null) {
            mViewRoot.requestInvalidateRootRenderNode();
            mViewRoot.getView().invalidate();
        }
    }

    void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
                          SparseArray<FrameTracker> runningTrackers) {
        mRunningCujs.put(removedCuj, reason);
        // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
        if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
            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).getViewRoot())) {
                        needsNewViewRoot = false;
                        break;
                    }
                }
            }
            if (needsNewViewRoot) {
                dispose();
                for (int i = 0; i < runningTrackers.size(); i++) {
                    if (attachViewRootIfNeeded(runningTrackers.valueAt(i).getViewRoot())) {
                        break;
                    }
                }
            } else {
                forceRedraw();
            }
        }
    }

    void onTrackerAdded(@CujType int addedCuj, FrameTracker.ViewRootWrapper viewRoot) {
        // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
        // is still running
        mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
        attachViewRootIfNeeded(viewRoot);
        forceRedraw();
    }

    @Override
    public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen,
                                       Rect systemInsets, Rect stableInsets) {
    }

    @Override
    public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
                                        Rect systemInsets, Rect stableInsets) {
    }

    @Override
    public void onWindowDragResizeEnd() {
    }

    @Override
    public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
        return false;
    }

    @Override
    public void onRequestDraw(boolean reportNextDraw) {
    }

    @Override
    public void onPostDraw(RecordingCanvas canvas) {
        Trace.beginSection("InteractionJankMonitor#drawDebug");
        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);
        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,
                padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
                mDebugPaint);
        mDebugPaint.setTextSize(packageNameFontSize);
        mDebugPaint.setColor(Color.BLACK);
        mDebugPaint.setStrikeThruText(false);
        canvas.translate(0, packageNameTextHeight);
        canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
        mDebugPaint.setTextSize(cujFontSize);
        // Draw text for CUJ names
        for (int i = 0; i < mRunningCujs.size(); i++) {
            int status = mRunningCujs.valueAt(i);
            if (status == REASON_STILL_RUNNING) {
                mDebugPaint.setColor(Color.BLACK);
                mDebugPaint.setStrikeThruText(false);
            } else if (status == REASON_END_NORMAL) {
                mDebugPaint.setColor(Color.GRAY);
                mDebugPaint.setStrikeThruText(false);
            } else {
                // Cancelled, or otherwise ended for a bad reason
                mDebugPaint.setColor(Color.RED);
                mDebugPaint.setStrikeThruText(true);
            }
            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
            canvas.translate(0, cujNameTextHeight);
            canvas.drawText(cujName, 0, 0, mDebugPaint);
        }
        Trace.endSection();
    }
}
+7 −4
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricManager;
@@ -113,13 +114,13 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.shared.system.PackageManagerWrapper;

import dagger.Module;
import dagger.Provides;

import java.util.Optional;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

/**
 * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
 */
@@ -323,7 +324,9 @@ public class FrameworkServicesModule {
    @Provides
    @Singleton
    static InteractionJankMonitor provideInteractionJankMonitor() {
        return InteractionJankMonitor.getInstance();
        InteractionJankMonitor jankMonitor = InteractionJankMonitor.getInstance();
        jankMonitor.configDebugOverlay(Color.YELLOW, 0.75);
        return jankMonitor;
    }

    @Provides