diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 8e7fae7aa061e..aa7142eaa39db 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -45,6 +45,7 @@ import android.view.ThreadedRenderer; import android.view.ViewRootImpl; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.jank.InteractionJankMonitor.Session; import com.android.internal.util.FrameworkStatsLog; @@ -69,6 +70,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener static final int REASON_CANCEL_NORMAL = 16; static final int REASON_CANCEL_NOT_BEGUN = 17; static final int REASON_CANCEL_SAME_VSYNC = 18; + static final int REASON_CANCEL_TIMEOUT = 19; /** @hide */ @IntDef({ @@ -97,6 +99,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private final Handler mHandler; private final ChoreographerWrapper mChoreographer; + @VisibleForTesting + public final boolean mSurfaceOnly; + private long mBeginVsyncId = INVALID_ID; private long mEndVsyncId = INVALID_ID; private boolean mMetricsFinalized; @@ -136,71 +141,86 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } public FrameTracker(@NonNull Session session, @NonNull Handler handler, - @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper, + @Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper, @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, - @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, - int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) { + @Nullable FrameMetricsWrapper metrics, + int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis, + @Nullable FrameTrackerListener listener, @NonNull Configuration config) { + mSurfaceOnly = config.isSurfaceOnly(); mSession = session; - mRendererWrapper = renderer; - mMetricsWrapper = metrics; - mViewRoot = viewRootWrapper; + mHandler = handler; mChoreographer = choreographer; mSurfaceControlWrapper = surfaceControlWrapper; - mHandler = handler; - mObserver = new HardwareRendererObserver( - this, mMetricsWrapper.getTiming(), handler, false /*waitForPresentTime*/); + + // HWUI instrumentation init. + mRendererWrapper = mSurfaceOnly ? null : renderer; + mMetricsWrapper = mSurfaceOnly ? null : metrics; + mViewRoot = mSurfaceOnly ? null : viewRootWrapper; + mObserver = mSurfaceOnly + ? null + : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), + handler, /* waitForPresentTime= */ false); + mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; mListener = listener; - // If the surface isn't valid yet, wait until it's created. - if (viewRootWrapper.getSurfaceControl().isValid()) { - mSurfaceControl = viewRootWrapper.getSurfaceControl(); + if (mSurfaceOnly) { + mSurfaceControl = config.getSurfaceControl(); + mSurfaceChangedCallback = null; + } else { + // HWUI instrumentation init. + // If the surface isn't valid yet, wait until it's created. + if (mViewRoot.getSurfaceControl().isValid()) { + mSurfaceControl = mViewRoot.getSurfaceControl(); + mSurfaceChangedCallback = null; + } else { + mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { + @Override + public void surfaceCreated(SurfaceControl.Transaction t) { + synchronized (FrameTracker.this) { + if (mSurfaceControl == null) { + mSurfaceControl = mViewRoot.getSurfaceControl(); + if (mBeginVsyncId != INVALID_ID) { + mSurfaceControlWrapper.addJankStatsListener( + FrameTracker.this, mSurfaceControl); + postTraceStartMarker(); + } + } + } + } + + @Override + public void surfaceReplaced(SurfaceControl.Transaction t) { + } + + @Override + public void surfaceDestroyed() { + + // Wait a while to give the system a chance for the remaining + // frames to arrive, then force finish the session. + mHandler.postDelayed(() -> { + synchronized (FrameTracker.this) { + if (DEBUG) { + Log.d(TAG, "surfaceDestroyed: " + mSession.getName() + + ", finalized=" + mMetricsFinalized + + ", info=" + mJankInfos.size() + + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId); + } + if (!mMetricsFinalized) { + end(REASON_END_SURFACE_DESTROYED); + finish(mJankInfos.size() - 1); + } + } + }, 50); + } + }; + // This callback has a reference to FrameTracker, + // remember to remove it to avoid leakage. + mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback); + } } - mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { - @Override - public void surfaceCreated(SurfaceControl.Transaction t) { - synchronized (FrameTracker.this) { - if (mSurfaceControl == null) { - mSurfaceControl = viewRootWrapper.getSurfaceControl(); - if (mBeginVsyncId != INVALID_ID) { - mSurfaceControlWrapper.addJankStatsListener( - FrameTracker.this, mSurfaceControl); - postTraceStartMarker(); - } - } - } - } - - @Override - public void surfaceReplaced(SurfaceControl.Transaction t) { - } - - @Override - public void surfaceDestroyed() { - - // Wait a while to give the system a chance for the remaining frames to arrive, then - // force finish the session. - mHandler.postDelayed(() -> { - synchronized (FrameTracker.this) { - if (DEBUG) { - Log.d(TAG, "surfaceDestroyed: " + mSession.getName() - + ", finalized=" + mMetricsFinalized - + ", info=" + mJankInfos.size() - + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId); - } - if (!mMetricsFinalized) { - end(REASON_END_SURFACE_DESTROYED); - finish(mJankInfos.size() - 1); - } - } - }, 50); - } - }; - - // This callback has a reference to FrameTracker, remember to remove it to avoid leakage. - viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback); } /** @@ -208,16 +228,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ public synchronized void begin() { mBeginVsyncId = mChoreographer.getVsyncId() + 1; - if (mSurfaceControl != null) { - postTraceStartMarker(); - } - mRendererWrapper.addObserver(mObserver); if (DEBUG) { Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId); } if (mSurfaceControl != null) { + postTraceStartMarker(); mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); } + if (!mSurfaceOnly) { + mRendererWrapper.addObserver(mObserver); + } if (mListener != null) { mListener.onCujEvents(mSession, ACTION_SESSION_BEGIN); } @@ -273,11 +293,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener * Cancel the trace session of the CUJ. */ public synchronized void cancel(@Reasons int reason) { + mCancelled = true; + // We don't need to end the trace section if it never begun. if (mTracingStarted) { Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); } - mCancelled = true; // Always remove the observers in cancel call to avoid leakage. removeObservers(); @@ -377,7 +398,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener for (int i = mJankInfos.size() - 1; i >= 0; i--) { JankInfo info = mJankInfos.valueAt(i); if (info.frameVsyncId >= mEndVsyncId) { - if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) { + if (isLastIndexCandidate(info)) { lastIndex = i; } } else { @@ -395,6 +416,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener finish(indexOnOrAfterEnd); } + private boolean isLastIndexCandidate(JankInfo info) { + return mSurfaceOnly + ? info.surfaceControlCallbackFired + : info.hwuiCallbackFired && info.surfaceControlCallbackFired; + } + private void finish(int indexOnOrAfterEnd) { mMetricsFinalized = true; @@ -410,7 +437,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener for (int i = 0; i <= indexOnOrAfterEnd; i++) { JankInfo info = mJankInfos.valueAt(i); - if (info.isFirstFrame) { + final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame; + if (isFirstDrawn) { continue; } if (info.surfaceControlCallbackFired) { @@ -435,11 +463,11 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } // TODO (b/174755489): Early latch currently gets fired way too often, so we have // to ignore it for now. - if (!info.hwuiCallbackFired) { + if (!mSurfaceOnly && !info.hwuiCallbackFired) { Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId); } } - if (info.hwuiCallbackFired) { + if (!mSurfaceOnly && info.hwuiCallbackFired) { maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos); if (!info.surfaceControlCallbackFired) { Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId); @@ -462,7 +490,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // Trigger perfetto if necessary. boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 && missedFramesCount >= mTraceThresholdMissedFrames; - boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1 + boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1 && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; if (overMissedFramesThreshold || overFrameTimeThreshold) { triggerPerfetto(); @@ -473,7 +501,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mSession.getStatsdInteractionType(), totalFramesCount, missedFramesCount, - maxFrameTimeNanos, + maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */ missedSfFramesCount, missedAppFramesCount); if (mListener != null) { @@ -496,10 +524,13 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ @VisibleForTesting public void removeObservers() { - mRendererWrapper.removeObserver(mObserver); mSurfaceControlWrapper.removeJankStatsListener(this); - if (mSurfaceChangedCallback != null) { - mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback); + if (!mSurfaceOnly) { + // HWUI part. + mRendererWrapper.removeObserver(mObserver); + if (mSurfaceChangedCallback != null) { + mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback); + } } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index aabcd7f82ac7d..aae6f50168917 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -18,11 +18,11 @@ package com.android.internal.jank; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; -import static com.android.internal.jank.FrameTracker.ChoreographerWrapper; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN; +import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; -import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; +import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; @@ -41,6 +41,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; @@ -58,6 +59,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; import android.annotation.IntDef; import android.annotation.NonNull; @@ -72,11 +74,15 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; +import android.view.SurfaceControl; import android.view.View; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.FrameTrackerListener; +import com.android.internal.jank.FrameTracker.Reasons; +import com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; @@ -103,7 +109,7 @@ public class InteractionJankMonitor { private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; - private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L); + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L); private static final String SETTINGS_ENABLED_KEY = "enabled"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY = @@ -163,6 +169,8 @@ public class InteractionJankMonitor { public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; + public static final int CUJ_PIP_TRANSITION = 35; + public static final int CUJ_WALLPAPER_TRANSITION = 36; private static final int NO_STATSD_LOGGING = -1; @@ -206,6 +214,8 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION, }; private static volatile InteractionJankMonitor sInstance; @@ -213,10 +223,10 @@ public class InteractionJankMonitor { private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = this::updateProperties; - private FrameMetricsWrapper mMetrics; - private SparseArray mRunningTrackers; - private SparseArray mTimeoutActions; - private HandlerThread mWorker; + private final FrameMetricsWrapper mMetrics; + private final SparseArray mRunningTrackers; + private final SparseArray mTimeoutActions; + private final HandlerThread mWorker; private boolean mEnabled = DEFAULT_ENABLED; private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; @@ -260,6 +270,8 @@ public class InteractionJankMonitor { CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + CUJ_PIP_TRANSITION, + CUJ_WALLPAPER_TRANSITION, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -310,24 +322,31 @@ public class InteractionJankMonitor { } /** - * Create a {@link FrameTracker} instance. + * Creates a {@link FrameTracker} instance. * + * @param config the config used in instrumenting * @param session the session associates with this tracker * @return instance of the FrameTracker */ @VisibleForTesting - public FrameTracker createFrameTracker(Configuration conf, Session session) { - final View v = conf.mView; - final Context c = v.getContext().getApplicationContext(); - final ThreadedRendererWrapper r = new ThreadedRendererWrapper(v.getThreadedRenderer()); - final ViewRootWrapper vr = new ViewRootWrapper(v.getViewRootImpl()); - final SurfaceControlWrapper sc = new SurfaceControlWrapper(); - final ChoreographerWrapper cg = new ChoreographerWrapper(Choreographer.getInstance()); + public FrameTracker createFrameTracker(Configuration config, Session session) { + final View view = config.mView; + final ThreadedRendererWrapper threadedRenderer = + view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer()); + final ViewRootWrapper viewRoot = + view == null ? null : new ViewRootWrapper(view.getViewRootImpl()); + + final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper(); + final ChoreographerWrapper choreographer = + new ChoreographerWrapper(Choreographer.getInstance()); synchronized (this) { - FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(c, act, s); - return new FrameTracker(session, mWorker.getThreadHandler(), r, vr, sc, cg, mMetrics, - mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener); + FrameTrackerListener eventsListener = + (s, act) -> handleCujEvents(config.getContext(), act, s); + return new FrameTracker(session, mWorker.getThreadHandler(), + threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics, + mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, + eventsListener, config); } } @@ -376,7 +395,7 @@ public class InteractionJankMonitor { } /** - * Begin a trace session. + * Begins a trace session. * * @param v an attached view. * @param cujType the specific {@link InteractionJankMonitor.CujType}. @@ -385,8 +404,7 @@ public class InteractionJankMonitor { public boolean begin(View v, @CujType int cujType) { try { return beginInternal( - new Configuration.Builder(cujType) - .setView(v) + Configuration.Builder.withView(cujType, v) .build()); } catch (IllegalArgumentException ex) { Log.d(TAG, "Build configuration failed!", ex); @@ -395,7 +413,7 @@ public class InteractionJankMonitor { } /** - * Begin a trace session. + * Begins a trace session. * * @param builder the builder of the configurations for instrumenting the CUJ. * @return boolean true if the tracker is started successfully, false otherwise. @@ -431,48 +449,60 @@ public class InteractionJankMonitor { tracker.begin(); // Cancel the trace if we don't get an end() call in specified duration. - Runnable timeoutAction = () -> cancel(cujType); - mTimeoutActions.put(cujType, timeoutAction); - mWorker.getThreadHandler().postDelayed(timeoutAction, conf.mTimeout); + scheduleTimeoutAction( + cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT)); return true; } } /** - * End a trace session. + * Schedules a timeout action. + * @param cuj cuj type + * @param timeout duration to timeout + * @param action action once timeout + */ + @VisibleForTesting + public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) { + mTimeoutActions.put(cuj, action); + mWorker.getThreadHandler().postDelayed(action, timeout); + } + + /** + * Ends a trace session. * * @param cujType the specific {@link InteractionJankMonitor.CujType}. * @return boolean true if the tracker is ended successfully, false otherwise. */ public boolean end(@CujType int cujType) { - //TODO (163505250): This should be no-op if not in droid food rom. synchronized (this) { - // remove the timeout action first. removeTimeout(cujType); FrameTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. if (tracker == null) return false; - tracker.end(FrameTracker.REASON_END_NORMAL); + tracker.end(REASON_END_NORMAL); removeTracker(cujType); return true; } } /** - * Cancel the trace session. + * Cancels the trace session. * * @return boolean true if the tracker is cancelled successfully, false otherwise. */ public boolean cancel(@CujType int cujType) { - //TODO (163505250): This should be no-op if not in droid food rom. + return cancel(cujType, REASON_CANCEL_NORMAL); + } + + boolean cancel(@CujType int cujType, @Reasons int reason) { synchronized (this) { // remove the timeout action first. removeTimeout(cujType); FrameTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. if (tracker == null) return false; - tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); + tracker.cancel(reason); removeTracker(cujType); return true; } @@ -509,7 +539,7 @@ public class InteractionJankMonitor { } /** - * Trigger the perfetto daemon to collect and upload data. + * Triggers the perfetto daemon to collect and upload data. */ @VisibleForTesting public void trigger(Session session) { @@ -608,6 +638,10 @@ public class InteractionJankMonitor { return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; + case CUJ_PIP_TRANSITION: + return "PIP_TRANSITION"; + case CUJ_WALLPAPER_TRANSITION: + return "WALLPAPER_TRANSITION"; } return "UNKNOWN"; } @@ -618,32 +652,64 @@ public class InteractionJankMonitor { */ public static class Configuration { private final View mView; + private final Context mContext; private final long mTimeout; private final String mTag; + private final boolean mSurfaceOnly; + private final SurfaceControl mSurfaceControl; private final @CujType int mCujType; /** - * A builder for building Configuration.
+ * A builder for building Configuration. {@link #setView(View)} is essential + * if {@link #setSurfaceOnly(boolean)} is not set, otherwise both + * {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)} + * are necessary
* It may refer to an attached view, don't use static reference for any purpose. */ public static class Builder { private View mAttrView = null; + private Context mAttrContext = null; private long mAttrTimeout = DEFAULT_TIMEOUT_MS; private String mAttrTag = ""; + private boolean mAttrSurfaceOnly; + private SurfaceControl mAttrSurfaceControl; private @CujType int mAttrCujType; /** + * Creates a builder which instruments only surface. * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param context context + * @param surfaceControl surface control + * @return builder */ - public Builder(@CujType int cuj) { + public static Builder withSurface(@CujType int cuj, @NonNull Context context, + @NonNull SurfaceControl surfaceControl) { + return new Builder(cuj) + .setContext(context) + .setSurfaceControl(surfaceControl) + .setSurfaceOnly(true); + } + + /** + * Creates a builder which instruments both surface and view. + * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param view view + * @return builder + */ + public static Builder withView(@CujType int cuj, @NonNull View view) { + return new Builder(cuj).setView(view); + } + + private Builder(@CujType int cuj) { mAttrCujType = cuj; } /** + * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false. * @param view an attached view * @return builder */ - public Builder setView(@NonNull View view) { + private Builder setView(@NonNull View view) { mAttrView = view; return this; } @@ -669,20 +735,56 @@ public class InteractionJankMonitor { } /** - * Build the {@link Configuration} instance + * Indicates if only instrument with surface, + * if true, must also setup with {@link #setContext(Context)} + * and {@link #setSurfaceControl(SurfaceControl)}. + * @param surfaceOnly true if only instrument with surface, false otherwise + * @return builder Surface only builder. + */ + private Builder setSurfaceOnly(boolean surfaceOnly) { + mAttrSurfaceOnly = surfaceOnly; + return this; + } + + /** + * Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set. + */ + private Builder setContext(Context context) { + mAttrContext = context; + return this; + } + + /** + * Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set. + */ + private Builder setSurfaceControl(SurfaceControl surfaceControl) { + mAttrSurfaceControl = surfaceControl; + return this; + } + + /** + * Builds the {@link Configuration} instance * @return the instance of {@link Configuration} * @throws IllegalArgumentException if any invalid attribute is set */ public Configuration build() throws IllegalArgumentException { - return new Configuration(mAttrCujType, mAttrView, mAttrTag, mAttrTimeout); + return new Configuration( + mAttrCujType, mAttrView, mAttrTag, mAttrTimeout, + mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl); } } - private Configuration(@CujType int cuj, View view, String tag, long timeout) { + private Configuration(@CujType int cuj, View view, String tag, long timeout, + boolean surfaceOnly, Context context, SurfaceControl surfaceControl) { mCujType = cuj; mTag = tag; mTimeout = timeout; mView = view; + mSurfaceOnly = surfaceOnly; + mContext = context != null + ? context + : (view != null ? view.getContext().getApplicationContext() : null); + mSurfaceControl = surfaceControl; validate(); } @@ -698,14 +800,47 @@ public class InteractionJankMonitor { shouldThrow = true; msg.append("Invalid timeout value; "); } - if (mView == null || !mView.isAttachedToWindow()) { - shouldThrow = true; - msg.append("Null view or view is not attached yet; "); + if (mSurfaceOnly) { + if (mContext == null) { + shouldThrow = true; + msg.append("Must pass in a context if only instrument surface; "); + } + if (mSurfaceControl == null || !mSurfaceControl.isValid()) { + shouldThrow = true; + msg.append("Must pass in a valid surface control if only instrument surface; "); + } + } else { + if (mView == null || !mView.isAttachedToWindow()) { + shouldThrow = true; + msg.append("Null view or unattached view while instrumenting view; "); + } } if (shouldThrow) { throw new IllegalArgumentException(msg.toString()); } } + + /** + * @return true if only instrumenting surface, false otherwise + */ + public boolean isSurfaceOnly() { + return mSurfaceOnly; + } + + /** + * @return the surafce control which is instrumenting + */ + public SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + + View getView() { + return mView; + } + + Context getContext() { + return mContext; + } } /** @@ -715,8 +850,8 @@ public class InteractionJankMonitor { @CujType private final int mCujType; private final long mTimeStamp; - @FrameTracker.Reasons - private int mReason = FrameTracker.REASON_END_UNKNOWN; + @Reasons + private int mReason = REASON_END_UNKNOWN; private final boolean mShouldNotify; private final String mName; @@ -756,15 +891,15 @@ public class InteractionJankMonitor { return mTimeStamp; } - public void setReason(@FrameTracker.Reasons int reason) { + public void setReason(@Reasons int reason) { mReason = reason; } - public int getReason() { + public @Reasons int getReason() { return mReason; } - /** Determine if should notify the receivers of cuj events */ + /** Determines if should notify the receivers of cuj events */ public boolean shouldNotify() { return mShouldNotify; } diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index 96b4316ffafcc..7cd8197ce1e4e 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -23,6 +23,7 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_ import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import static com.android.internal.jank.FrameTracker.ViewRootWrapper; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION; import static com.google.common.truth.Truth.assertThat; @@ -50,6 +51,7 @@ import androidx.test.rule.ActivityTestRule; import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; +import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.jank.InteractionJankMonitor.Session; import org.junit.Before; @@ -69,7 +71,6 @@ public class FrameTrackerTest { public ActivityTestRule mRule = new ActivityTestRule<>(ViewAttachTestActivity.class); - private FrameTracker mTracker; private ThreadedRendererWrapper mRenderer; private FrameMetricsWrapper mWrapper; private SurfaceControlWrapper mSurfaceControlWrapper; @@ -85,7 +86,6 @@ public class FrameTrackerTest { View view = mActivity.getWindow().getDecorView(); assertThat(view.isAttachedToWindow()).isTrue(); - Handler handler = mRule.getActivity().getMainThreadHandler(); mWrapper = Mockito.spy(new FrameMetricsWrapper()); mRenderer = Mockito.spy(new ThreadedRendererWrapper(view.getThreadedRenderer())); doNothing().when(mRenderer).addObserver(any()); @@ -103,229 +103,355 @@ public class FrameTrackerTest { mListenerCapture.capture()); mChoreographer = mock(ChoreographerWrapper.class); + } - Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX); - mTracker = Mockito.spy( + private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) { + Handler handler = mRule.getActivity().getMainThreadHandler(); + Session session = new Session(cuj, postfix); + Configuration config = mock(Configuration.class); + when(config.isSurfaceOnly()).thenReturn(surfaceOnly); + when(config.getSurfaceControl()).thenReturn(mSurfaceControl); + FrameTracker frameTracker = Mockito.spy( new FrameTracker(session, handler, mRenderer, mViewRootWrapper, mSurfaceControlWrapper, mChoreographer, mWrapper, - /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1, - null)); - doNothing().when(mTracker).triggerPerfetto(); - doNothing().when(mTracker).postTraceStartMarker(); + /* traceThresholdMissedFrames= */ 1, + /* traceThresholdFrameTimeMillis= */ -1, + /* FrameTrackerListener= */ null, config)); + doNothing().when(frameTracker).triggerPerfetto(); + doNothing().when(frameTracker).postTraceStartMarker(); + return frameTracker; } @Test public void testOnlyFirstWindowFrameOverThreshold() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) .then(unusedInvocation -> System.nanoTime()); when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame with a long duration - should not be taken into account - sendFirstWindowFrame(100, JANK_APP_DEADLINE_MISSED, 100L); + sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L); // send another frame with a short duration - should not be considered janky - sendFirstWindowFrame(5, JANK_NONE, 101L); + sendFirstWindowFrame(tracker, 5, JANK_NONE, 101L); // end the trace session, the last janky frame is after the end() so is discarded. when(mChoreographer.getVsyncId()).thenReturn(102L); - mTracker.end(FrameTracker.REASON_END_NORMAL); - sendFrame(5, JANK_NONE, 102L); - sendFrame(500, JANK_APP_DEADLINE_MISSED, 103L); + tracker.end(FrameTracker.REASON_END_NORMAL); + sendFrame(tracker, 5, JANK_NONE, 102L); + sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L); - verify(mTracker).removeObservers(); - verify(mTracker, never()).triggerPerfetto(); + verify(tracker).removeObservers(); + verify(tracker, never()).triggerPerfetto(); } @Test public void testSfJank() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame - not janky - sendFrame(4, JANK_NONE, 100L); + sendFrame(tracker, 4, JANK_NONE, 100L); // send another frame - should be considered janky - sendFrame(40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L); + sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L); // end the trace session when(mChoreographer.getVsyncId()).thenReturn(102L); - mTracker.end(FrameTracker.REASON_END_NORMAL); - sendFrame(4, JANK_NONE, 102L); + tracker.end(FrameTracker.REASON_END_NORMAL); + sendFrame(tracker, 4, JANK_NONE, 102L); - verify(mTracker).removeObservers(); + verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(mTracker).triggerPerfetto(); + verify(tracker).triggerPerfetto(); } @Test public void testFirstFrameJankyNoTrigger() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame - janky - sendFrame(40, JANK_APP_DEADLINE_MISSED, 100L); + sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L); // send another frame - not jank - sendFrame(4, JANK_NONE, 101L); + sendFrame(tracker, 4, JANK_NONE, 101L); // end the trace session when(mChoreographer.getVsyncId()).thenReturn(102L); - mTracker.end(FrameTracker.REASON_END_NORMAL); - sendFrame(4, JANK_NONE, 102L); + tracker.end(FrameTracker.REASON_END_NORMAL); + sendFrame(tracker, 4, JANK_NONE, 102L); - verify(mTracker).removeObservers(); + verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(mTracker, never()).triggerPerfetto(); + verify(tracker, never()).triggerPerfetto(); } @Test public void testOtherFrameOverThreshold() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame - not janky - sendFrame(4, JANK_NONE, 100L); + sendFrame(tracker, 4, JANK_NONE, 100L); // send another frame - should be considered janky - sendFrame(40, JANK_APP_DEADLINE_MISSED, 101L); + sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L); // end the trace session when(mChoreographer.getVsyncId()).thenReturn(102L); - mTracker.end(FrameTracker.REASON_END_NORMAL); - sendFrame(4, JANK_NONE, 102L); + tracker.end(FrameTracker.REASON_END_NORMAL); + sendFrame(tracker, 4, JANK_NONE, 102L); - verify(mTracker).removeObservers(); + verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(mTracker).triggerPerfetto(); + verify(tracker).triggerPerfetto(); } @Test public void testLastFrameOverThresholdBeforeEnd() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame - not janky - sendFrame(4, JANK_NONE, 100L); + sendFrame(tracker, 4, JANK_NONE, 100L); // send another frame - not janky - sendFrame(4, JANK_NONE, 101L); + sendFrame(tracker, 4, JANK_NONE, 101L); // end the trace session, simulate one more valid callback came after the end call. when(mChoreographer.getVsyncId()).thenReturn(102L); - mTracker.end(FrameTracker.REASON_END_NORMAL); - sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L); + tracker.end(FrameTracker.REASON_END_NORMAL); + sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L); // One more callback with VSYNC after the end() vsync id. - sendFrame(4, JANK_NONE, 103L); + sendFrame(tracker, 4, JANK_NONE, 103L); - verify(mTracker).removeObservers(); + verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(mTracker).triggerPerfetto(); + verify(tracker).triggerPerfetto(); } @Test public void testBeginCancel() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer).addObserver(any()); // First frame - not janky - sendFrame(4, JANK_NONE, 100L); + sendFrame(tracker, 4, JANK_NONE, 100L); // normal frame - not janky - sendFrame(4, JANK_NONE, 101L); + sendFrame(tracker, 4, JANK_NONE, 101L); // a janky frame - sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L); + sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L); - mTracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); - verify(mTracker).removeObservers(); + tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); + verify(tracker).removeObservers(); // Since the tracker has been cancelled, shouldn't trigger perfetto. - verify(mTracker, never()).triggerPerfetto(); + verify(tracker, never()).triggerPerfetto(); } @Test public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // end the trace session when(mChoreographer.getVsyncId()).thenReturn(101L); - mTracker.end(FrameTracker.REASON_END_NORMAL); + tracker.end(FrameTracker.REASON_END_NORMAL); // Since the begin vsync id (101) equals to the end vsync id (101), will be treat as cancel. - verify(mTracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC); + verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC); // Observers should be removed in this case, or FrameTracker object will be leaked. - verify(mTracker).removeObservers(); + verify(tracker).removeObservers(); // Should never trigger Perfetto since it is a cancel. - verify(mTracker, never()).triggerPerfetto(); + verify(tracker, never()).triggerPerfetto(); } @Test public void testCancelIfEndVsyncIdLessThanBeginVsyncId() { + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + when(mChoreographer.getVsyncId()).thenReturn(100L); - mTracker.begin(); + tracker.begin(); verify(mRenderer, only()).addObserver(any()); // end the trace session at the same vsync id, end vsync id will less than the begin one. // Because the begin vsync id is supposed to the next frame, - mTracker.end(FrameTracker.REASON_END_NORMAL); + tracker.end(FrameTracker.REASON_END_NORMAL); // The begin vsync id (101) is larger than the end one (100), will be treat as cancel. - verify(mTracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC); + verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC); // Observers should be removed in this case, or FrameTracker object will be leaked. - verify(mTracker).removeObservers(); + verify(tracker).removeObservers(); // Should never trigger Perfetto since it is a cancel. - verify(mTracker, never()).triggerPerfetto(); + verify(tracker, never()).triggerPerfetto(); } @Test public void testCancelWhenSessionNeverBegun() { - mTracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); - verify(mTracker).removeObservers(); + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + + tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); + verify(tracker).removeObservers(); } @Test public void testEndWhenSessionNeverBegun() { - mTracker.end(FrameTracker.REASON_END_NORMAL); - verify(mTracker).removeObservers(); + FrameTracker tracker = spyFrameTracker( + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + + tracker.end(FrameTracker.REASON_END_NORMAL); + verify(tracker).removeObservers(); } - private void sendFirstWindowFrame(long durationMillis, + @Test + public void testSurfaceOnlyOtherFrameJanky() { + FrameTracker tracker = spyFrameTracker( + CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + + when(mChoreographer.getVsyncId()).thenReturn(100L); + tracker.begin(); + verify(mSurfaceControlWrapper).addJankStatsListener(any(), any()); + + // First frame - not janky + sendFrame(tracker, JANK_NONE, 100L); + // normal frame - not janky + sendFrame(tracker, JANK_NONE, 101L); + // a janky frame + sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L); + + when(mChoreographer.getVsyncId()).thenReturn(102L); + tracker.end(FrameTracker.REASON_CANCEL_NORMAL); + + // an extra frame to trigger finish + sendFrame(tracker, JANK_NONE, 103L); + + verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(tracker).triggerPerfetto(); + } + + @Test + public void testSurfaceOnlyFirstFrameJanky() { + FrameTracker tracker = spyFrameTracker( + CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + + when(mChoreographer.getVsyncId()).thenReturn(100L); + tracker.begin(); + verify(mSurfaceControlWrapper).addJankStatsListener(any(), any()); + + // First frame - janky + sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L); + // normal frame - not janky + sendFrame(tracker, JANK_NONE, 101L); + // normal frame - not janky + sendFrame(tracker, JANK_NONE, 102L); + + when(mChoreographer.getVsyncId()).thenReturn(102L); + tracker.end(FrameTracker.REASON_CANCEL_NORMAL); + + // an extra frame to trigger finish + sendFrame(tracker, JANK_NONE, 103L); + + verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(tracker, never()).triggerPerfetto(); + } + + @Test + public void testSurfaceOnlyLastFrameJanky() { + FrameTracker tracker = spyFrameTracker( + CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + + when(mChoreographer.getVsyncId()).thenReturn(100L); + tracker.begin(); + verify(mSurfaceControlWrapper).addJankStatsListener(any(), any()); + + // First frame - not janky + sendFrame(tracker, JANK_NONE, 100L); + // normal frame - not janky + sendFrame(tracker, JANK_NONE, 101L); + // normal frame - not janky + sendFrame(tracker, JANK_NONE, 102L); + + when(mChoreographer.getVsyncId()).thenReturn(102L); + tracker.end(FrameTracker.REASON_CANCEL_NORMAL); + + // janky frame, should be ignored, trigger finish + sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L); + + verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(tracker, never()).triggerPerfetto(); + } + + private void sendFirstWindowFrame(FrameTracker tracker, long durationMillis, @JankType int jankType, long vsyncId) { - sendFrame(durationMillis, jankType, vsyncId, true /* firstWindowFrame */); + sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ true); } - private void sendFrame(long durationMillis, + private void sendFrame(FrameTracker tracker, long durationMillis, @JankType int jankType, long vsyncId) { - sendFrame(durationMillis, jankType, vsyncId, false /* firstWindowFrame */); + sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ false); } - private void sendFrame(long durationMillis, + /** + * Used for surface only test. + */ + private void sendFrame(FrameTracker tracker, @JankType int jankType, long vsyncId) { + sendFrame(tracker, /* durationMillis= */ -1, + jankType, vsyncId, /* firstWindowFrame= */ false); + } + + private void sendFrame(FrameTracker tracker, long durationMillis, @JankType int jankType, long vsyncId, boolean firstWindowFrame) { - when(mWrapper.getTiming()).thenReturn(new long[] { 0, vsyncId }); - doReturn(firstWindowFrame ? 1L : 0L).when(mWrapper) - .getMetric(FrameMetrics.FIRST_DRAW_FRAME); - doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis)) - .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION); - mTracker.onFrameMetricsAvailable(0); + if (!tracker.mSurfaceOnly) { + when(mWrapper.getTiming()).thenReturn(new long[]{0, vsyncId}); + doReturn(firstWindowFrame ? 1L : 0L).when(mWrapper) + .getMetric(FrameMetrics.FIRST_DRAW_FRAME); + doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis)) + .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION); + tracker.onFrameMetricsAvailable(0); + } mListenerCapture.getValue().onJankDataAvailable(new JankData[] { new JankData(vsyncId, jankType) }); diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index c153b38d3f020..d7a5e26131756 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -16,8 +16,6 @@ package com.android.internal.jank; -import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; -import static com.android.internal.jank.FrameTracker.ViewRootWrapper; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; @@ -25,17 +23,17 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.os.Handler; import android.os.HandlerThread; -import android.os.Message; import android.provider.DeviceConfig; import android.view.View; import android.view.ViewAttachTestActivity; @@ -43,8 +41,12 @@ import android.view.ViewAttachTestActivity; import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; +import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; +import com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; +import com.android.internal.jank.FrameTracker.ViewRootWrapper; +import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.jank.InteractionJankMonitor.Session; import org.junit.Before; @@ -92,12 +94,15 @@ public class InteractionJankMonitorTest { verify(mWorker).start(); Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX); + Configuration config = mock(Configuration.class); + when(config.isSurfaceOnly()).thenReturn(false); FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(mView.getThreadedRenderer()), - new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), - mock(FrameTracker.ChoreographerWrapper.class), - new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, - /*traceThresholdFrameTimeMillis=*/ -1, null)); + new ViewRootWrapper(mView.getViewRootImpl()), + new SurfaceControlWrapper(), mock(ChoreographerWrapper.class), + new FrameMetricsWrapper(), + /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, + /* FrameTrackerListener */ null, config)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); doNothing().when(tracker).triggerPerfetto(); doNothing().when(tracker).postTraceStartMarker(); @@ -138,28 +143,30 @@ public class InteractionJankMonitorTest { public void testBeginCancel() { InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); - ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX); + Configuration config = mock(Configuration.class); + when(config.isSurfaceOnly()).thenReturn(false); FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(mView.getThreadedRenderer()), - new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), - mock(FrameTracker.ChoreographerWrapper.class), - new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, - /*traceThresholdFrameTimeMillis=*/ -1, null)); + new ViewRootWrapper(mView.getViewRootImpl()), + new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), + new FrameMetricsWrapper(), + /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, + /* FrameTrackerListener */ null, config)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); doNothing().when(tracker).triggerPerfetto(); doNothing().when(tracker).postTraceStartMarker(); assertThat(monitor.begin(mView, session.getCuj())).isTrue(); verify(tracker).begin(); - verify(mWorker.getThreadHandler(), atLeastOnce()).sendMessageAtTime(captor.capture(), - anyLong()); - Runnable runnable = captor.getValue().getCallback(); + verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture()); + Runnable runnable = captor.getValue(); assertThat(runnable).isNotNull(); mWorker.getThreadHandler().removeCallbacks(runnable); runnable.run(); - verify(tracker).cancel(FrameTracker.REASON_CANCEL_NORMAL); + verify(tracker).cancel(FrameTracker.REASON_CANCEL_TIMEOUT); } @Test diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index 442716878af43..b82d896e6bea9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -80,8 +80,7 @@ public final class InteractionJankMonitorWrapper { public static void begin(View v, @CujType int cujType, long timeout) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return; Configuration.Builder builder = - new Configuration.Builder(cujType) - .setView(v) + Configuration.Builder.withView(cujType, v) .setTimeout(timeout); InteractionJankMonitor.getInstance().begin(builder); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d12c76334454d..0cbf5d03b9cec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2324,8 +2324,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } private Configuration.Builder createInteractionJankMonitorConf(String tag) { - return new Configuration.Builder(CUJ_LOCKSCREEN_UNLOCK_ANIMATION) - .setView(mKeyguardViewControllerLazy.get().getViewRootImpl().getView()) + return Configuration.Builder.withView(CUJ_LOCKSCREEN_UNLOCK_ANIMATION, + mKeyguardViewControllerLazy.get().getViewRootImpl().getView()) .setTag(tag); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 4f3406c405add..7a71ee1d154e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -521,8 +521,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { mWasCancelled = false; - Configuration.Builder builder = new Configuration.Builder(getCujType(isAppearing)) - .setView(ActivatableNotificationView.this); + Configuration.Builder builder = Configuration.Builder + .withView(getCujType(isAppearing), ActivatableNotificationView.this); InteractionJankMonitor.getInstance().begin(builder); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 323a1128d3bbc..27f08a860f28b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -1397,8 +1397,7 @@ public abstract class PanelViewController { private void beginJankMonitoring(int cuj) { InteractionJankMonitor.Configuration.Builder builder = - new InteractionJankMonitor.Configuration.Builder(cuj) - .setView(mView) + InteractionJankMonitor.Configuration.Builder.withView(cuj, mView) .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); InteractionJankMonitor.getInstance().begin(builder); }