• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.jank;
18 
19 import static android.Manifest.permission.READ_DEVICE_CONFIG;
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR;
22 
23 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
24 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
25 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
26 
27 import android.Manifest;
28 import android.annotation.ColorInt;
29 import android.annotation.NonNull;
30 import android.annotation.RequiresPermission;
31 import android.annotation.UiThread;
32 import android.annotation.WorkerThread;
33 import android.app.ActivityThread;
34 import android.app.Application;
35 import android.content.Context;
36 import android.graphics.Color;
37 import android.os.Build;
38 import android.os.Handler;
39 import android.os.HandlerExecutor;
40 import android.os.HandlerThread;
41 import android.os.SystemClock;
42 import android.provider.DeviceConfig;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.util.SparseArray;
46 import android.view.Choreographer;
47 import android.view.SurfaceControl;
48 import android.view.View;
49 
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
53 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
54 import com.android.internal.jank.FrameTracker.FrameTrackerListener;
55 import com.android.internal.jank.FrameTracker.Reasons;
56 import com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
57 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
58 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
59 import com.android.internal.util.PerfettoTrigger;
60 
61 import java.time.Instant;
62 import java.util.concurrent.ThreadLocalRandom;
63 import java.util.concurrent.TimeUnit;
64 import java.util.function.Supplier;
65 
66 /**
67  * This class lets users begin and end the always on tracing mechanism.
68  *
69  * Enabling for local development:
70  *<pre>
71  * adb shell device_config put interaction_jank_monitor enabled true
72  * adb shell device_config put interaction_jank_monitor sampling_interval 1
73  * </pre>
74  * On debuggable builds, an overlay can be used to display the name of the
75  * currently running cuj using:
76  * <pre>
77  * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
78  * </pre>
79  * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used
80  * for understanding which UI events correspond to which CUJs.
81  *
82  * @hide
83  */
84 public class InteractionJankMonitor {
85     private static final String TAG = InteractionJankMonitor.class.getSimpleName();
86     private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
87 
88     private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
89     private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
90     static final long EXECUTOR_TASK_TIMEOUT = 500;
91     private static final String SETTINGS_ENABLED_KEY = "enabled";
92     private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
93     private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
94             "trace_threshold_missed_frames";
95     private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
96             "trace_threshold_frame_time_millis";
97     private static final String SETTINGS_DEBUG_OVERLAY_ENABLED_KEY = "debug_overlay_enabled";
98     /** Default to being enabled on debug builds. */
99     private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
100     /** Default to collecting data for all CUJs. */
101     private static final int DEFAULT_SAMPLING_INTERVAL = 1;
102     /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
103     private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
104     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
105     private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;
106 
107     private static final int MAX_LENGTH_SESSION_NAME = 100;
108 
109     public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
110     public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
111 
112     // These are not the CUJ constants you are looking for. These constants simply forward their
113     // definition from {@link Cuj}. They are here only as a transition measure until all references
114     // have been updated to the new location.
115     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
116     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
117     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
118     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
119     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
120     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
121     @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
122     @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
123     @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD;
124     @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE;
125     @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START;
126     @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR;
127     @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR;
128     @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR;
129     @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR;
130     @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR;
131     @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
132     @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
133     @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
134     @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL;
135     @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
136     @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
137     @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
138     @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE;
139     @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
140     @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
141     @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION;
142     @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH;
143     @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD;
144     @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM;
145     @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF;
146     @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD;
147     @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM;
148     @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
149     @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
150     @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW;
151     @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS;
152     @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE;
153     @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER;
154     @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT;
155     @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL;
156     @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION;
157     @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE;
158     @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN;
159     @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN;
160     @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND;
161     @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE;
162     @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL;
163     @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION;
164     @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
165     @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
166     @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
167     @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK;
168     @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME;
169     @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
170     @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
171     @Deprecated public static final int CUJ_LAUNCHER_SAVE_APP_PAIR = Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR;
172     @Deprecated public static final int CUJ_LAUNCHER_ALL_APPS_SEARCH_BACK = Cuj.CUJ_LAUNCHER_ALL_APPS_SEARCH_BACK;
173     @Deprecated public static final int CUJ_LAUNCHER_TASKBAR_ALL_APPS_CLOSE_BACK = Cuj.CUJ_LAUNCHER_TASKBAR_ALL_APPS_CLOSE_BACK;
174     @Deprecated public static final int CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK = Cuj.CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK;
175     @Deprecated public static final int CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK = Cuj.CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK;
176     @Deprecated public static final int CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK = Cuj.CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK;
177     @Deprecated public static final int CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK = Cuj.CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK;
178     @Deprecated public static final int CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK = Cuj.CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
179 
180     private static class InstanceHolder {
181         public static final InteractionJankMonitor INSTANCE =
182             new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
183     }
184 
185     @GuardedBy("mLock")
186     private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
187     private final Handler mWorker;
188     private final Application mCurrentApplication;
189     private final DisplayResolutionTracker mDisplayResolutionTracker;
190     private final Object mLock = new Object();
191     private @ColorInt int mDebugBgColor = Color.CYAN;
192     private double mDebugYOffset = 0.1;
193     @GuardedBy("mLock")
194     private InteractionMonitorDebugOverlay mDebugOverlay;
195 
196     private volatile boolean mEnabled = DEFAULT_ENABLED;
197     private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
198     private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
199     private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
200 
201     /**
202      * Get the singleton of InteractionJankMonitor.
203      *
204      * @return instance of InteractionJankMonitor
205      */
getInstance()206     public static InteractionJankMonitor getInstance() {
207         return InstanceHolder.INSTANCE;
208     }
209 
210     /**
211      * This constructor should be only public to tests.
212      *
213      * @param worker the worker thread for the callbacks
214      */
215     @VisibleForTesting
216     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
InteractionJankMonitor(@onNull HandlerThread worker)217     public InteractionJankMonitor(@NonNull HandlerThread worker) {
218         worker.start();
219         mWorker = worker.getThreadHandler();
220         mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
221 
222         mCurrentApplication = ActivityThread.currentApplication();
223         if (mCurrentApplication == null || mCurrentApplication.checkCallingOrSelfPermission(
224                 READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
225             Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
226                     + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
227                     + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
228                     + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
229                     + ", package=" + (mCurrentApplication == null ? "null"
230                     : mCurrentApplication.getPackageName()));
231             return;
232         }
233 
234         // Post initialization to the background in case we're running on the main thread.
235         mWorker.post(() -> {
236             try {
237                 updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
238                 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR,
239                         new HandlerExecutor(mWorker), this::updateProperties);
240             } catch (SecurityException ex) {
241                 Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
242                         + mCurrentApplication.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
243                         + ", package=" + mCurrentApplication.getPackageName());
244             }
245         });
246     }
247 
248     /**
249      * Creates a {@link FrameTracker} instance.
250      *
251      * @param config the conifg associates with this tracker
252      * @return instance of the FrameTracker
253      */
254     @VisibleForTesting
createFrameTracker(Configuration config)255     public FrameTracker createFrameTracker(Configuration config) {
256         final View view = config.mView;
257 
258         final ThreadedRendererWrapper threadedRenderer =
259                 view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
260         final ViewRootWrapper viewRoot =
261                 view == null ? null : new ViewRootWrapper(view.getViewRootImpl());
262         final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
263         final ChoreographerWrapper choreographer =
264                 new ChoreographerWrapper(Choreographer.getInstance());
265         final FrameTrackerListener eventsListener = new FrameTrackerListener() {
266             @Override
267             public void onCujEvents(FrameTracker tracker, String action, int reason) {
268                 config.getHandler().runWithScissors(() ->
269                         handleCujEvents(config.mCujType, tracker, action, reason),
270                         EXECUTOR_TASK_TIMEOUT);
271             }
272 
273             @Override
274             public void triggerPerfetto(Configuration config) {
275                 mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger()));
276             }
277         };
278         final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper();
279 
280         return new FrameTracker(config, threadedRenderer, viewRoot,
281                 surfaceControl, choreographer, frameMetrics,
282                 new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker),
283                 mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
284                 eventsListener);
285     }
286 
287     @UiThread
handleCujEvents( @uj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason)288     private void handleCujEvents(
289             @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) {
290         // Clear the running and timeout tasks if the end / cancel was fired within the tracker.
291         // Or we might have memory leaks.
292         if (needRemoveTasks(action, reason)) {
293             removeTrackerIfCurrent(cuj, tracker, reason);
294         }
295     }
296 
needRemoveTasks(String action, @Reasons int reason)297     private static boolean needRemoveTasks(String action, @Reasons int reason) {
298         final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL;
299         final boolean badCancel = action.equals(ACTION_SESSION_CANCEL)
300                 && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT);
301         return badEnd || badCancel;
302     }
303 
304     /**
305      * @param cujType cuj type
306      * @return true if the cuj is under instrumenting, false otherwise.
307      */
isInstrumenting(@uj.CujType int cujType)308     public boolean isInstrumenting(@Cuj.CujType int cujType) {
309         synchronized (mLock) {
310             return mRunningTrackers.contains(cujType);
311         }
312     }
313 
314     /**
315      * Begins a trace session.
316      *
317      * @param v an attached view.
318      * @param cujType the specific {@link Cuj.CujType}.
319      * @return boolean true if the tracker is started successfully, false otherwise.
320      */
begin(View v, @Cuj.CujType int cujType)321     public boolean begin(View v, @Cuj.CujType int cujType) {
322         try {
323             return begin(Configuration.Builder.withView(cujType, v));
324         } catch (IllegalArgumentException ex) {
325             Log.d(TAG, "Build configuration failed!", ex);
326             return false;
327         }
328     }
329 
330     /**
331      * Begins a trace session.
332      *
333      * @param surface a handle for the surface to begin tracing for.
334      * @param context context to provide display and handler information.
335      * @param cujType the specific {@link Cuj.CujType}.
336      * @return boolean true if the tracker is started successfully, false otherwise.
337      */
begin(SurfaceControl surface, Context context, Handler handler, @Cuj.CujType int cujType)338     public boolean begin(SurfaceControl surface, Context context, Handler handler,
339             @Cuj.CujType int cujType) {
340         try {
341             return begin(Configuration.Builder.withSurface(cujType, context, surface, handler));
342         } catch (IllegalArgumentException ex) {
343             Log.d(TAG, "Build configuration failed!", ex);
344             return false;
345         }
346     }
347 
348     /**
349      * Begins a trace session.
350      *
351      * @param surface a handle for the surface to begin tracing for.
352      * @param context context to provide display and handler information.
353      * @param cujType the specific {@link Cuj.CujType}.
354      * @param tag a tag containing extra information about the interaction.
355      * @return boolean true if the tracker is started successfully, false otherwise.
356      */
begin(SurfaceControl surface, Context context, Handler handler, @Cuj.CujType int cujType, String tag)357     public boolean begin(SurfaceControl surface, Context context, Handler handler,
358             @Cuj.CujType int cujType,
359             String tag) {
360         try {
361             final Configuration.Builder builder =
362                     Configuration.Builder.withSurface(cujType, context, surface, handler);
363             if (!TextUtils.isEmpty(tag)) {
364                 builder.setTag(tag);
365             }
366             return begin(builder);
367         } catch (IllegalArgumentException ex) {
368             Log.d(TAG, "Build configuration failed!", ex);
369             return false;
370         }
371     }
372 
373 
374     /**
375      * Begins a trace session.
376      *
377      * @param builder the builder of the configurations for instrumenting the CUJ.
378      * @return boolean true if the tracker is begun successfully, false otherwise.
379      */
begin(@onNull Configuration.Builder builder)380     public boolean begin(@NonNull Configuration.Builder builder) {
381         try {
382             final Configuration config = builder.build();
383             postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
384                 EventLogTags.writeJankCujEventsBeginRequest(
385                         config.mCujType, unixNanos, elapsedNanos, realtimeNanos, config.mTag);
386             });
387             final TrackerResult result = new TrackerResult();
388             final boolean success = config.getHandler().runWithScissors(
389                     () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
390             if (!success) {
391                 Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType));
392                 return false;
393             }
394             return result.mResult;
395         } catch (IllegalArgumentException ex) {
396             Log.d(TAG, "Build configuration failed!", ex);
397             return false;
398         }
399     }
400 
401     @UiThread
beginInternal(@onNull Configuration conf)402     private boolean beginInternal(@NonNull Configuration conf) {
403         int cujType = conf.mCujType;
404         if (!shouldMonitor()) {
405             return false;
406         } else if (!conf.hasValidView()) {
407             Log.w(TAG, "The view has since become invalid, aborting the CUJ.");
408             return false;
409         }
410 
411         RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
412                 new RunningTracker(
413                     conf, createFrameTracker(conf), () -> {
414                         Log.w(TAG, "CUJ cancelled due to timeout, CUJ="
415                                 + Cuj.getNameOfCuj(cujType));
416                         cancel(cujType, REASON_CANCEL_TIMEOUT);
417                     }));
418         if (tracker == null) {
419             return false;
420         }
421 
422         tracker.mTracker.begin();
423         // Cancel the trace if we don't get an end() call in specified duration.
424         scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction);
425 
426         return true;
427     }
428 
429     /**
430      * Check if the monitoring is enabled and if it should be sampled.
431      */
432     @VisibleForTesting
shouldMonitor()433     public boolean shouldMonitor() {
434         return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0);
435     }
436 
437     @VisibleForTesting
scheduleTimeoutAction(Configuration config, Runnable action)438     public void scheduleTimeoutAction(Configuration config, Runnable action) {
439         config.getHandler().postDelayed(action, config.mTimeout);
440     }
441 
442     /**
443      * Ends a trace session.
444      *
445      * @param cujType the specific {@link Cuj.CujType}.
446      * @return boolean true if the tracker is ended successfully, false otherwise.
447      */
end(@uj.CujType int cujType)448     public boolean end(@Cuj.CujType int cujType) {
449         postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
450             EventLogTags.writeJankCujEventsEndRequest(
451                     cujType, unixNanos, elapsedNanos, realtimeNanos);
452         });
453         RunningTracker tracker = getTracker(cujType);
454         // Skip this call since we haven't started a trace yet.
455         if (tracker == null) {
456             return false;
457         }
458         try {
459             final TrackerResult result = new TrackerResult();
460             final boolean success = tracker.mConfig.getHandler().runWithScissors(
461                     () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT);
462             if (!success) {
463                 Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
464                 return false;
465             }
466             return result.mResult;
467         } catch (IllegalArgumentException ex) {
468             Log.d(TAG, "Execute end task failed!", ex);
469             return false;
470         }
471     }
472 
473     @UiThread
endInternal(RunningTracker tracker)474     private boolean endInternal(RunningTracker tracker) {
475         if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) {
476             return false;
477         }
478         tracker.mTracker.end(REASON_END_NORMAL);
479         return true;
480     }
481 
482     /**
483      * Cancels the trace session.
484      *
485      * @return boolean true if the tracker is cancelled successfully, false otherwise.
486      */
cancel(@uj.CujType int cujType)487     public boolean cancel(@Cuj.CujType int cujType) {
488         postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
489             EventLogTags.writeJankCujEventsCancelRequest(
490                     cujType, unixNanos, elapsedNanos, realtimeNanos);
491         });
492         return cancel(cujType, REASON_CANCEL_NORMAL);
493     }
494 
495     /**
496      * Cancels the trace session.
497      *
498      * @return boolean true if the tracker is cancelled successfully, false otherwise.
499      */
500     @VisibleForTesting
cancel(@uj.CujType int cujType, @Reasons int reason)501     public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) {
502         RunningTracker tracker = getTracker(cujType);
503         // Skip this call since we haven't started a trace yet.
504         if (tracker == null) {
505             return false;
506         }
507         try {
508             final TrackerResult result = new TrackerResult();
509             final boolean success = tracker.mConfig.getHandler().runWithScissors(
510                     () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT);
511             if (!success) {
512                 Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
513                 return false;
514             }
515             return result.mResult;
516         } catch (IllegalArgumentException ex) {
517             Log.d(TAG, "Execute cancel task failed!", ex);
518             return false;
519         }
520     }
521 
522     @UiThread
cancelInternal(RunningTracker tracker, @Reasons int reason)523     private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) {
524         if (removeTrackerIfCurrent(tracker, reason)) {
525             return false;
526         }
527         tracker.mTracker.cancel(reason);
528         return true;
529     }
530 
531     @UiThread
putTrackerIfNoCurrent( @uj.CujType int cuj, Supplier<RunningTracker> supplier)532     private RunningTracker putTrackerIfNoCurrent(
533             @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) {
534         synchronized (mLock) {
535             if (mRunningTrackers.contains(cuj)) {
536                 return null;
537             }
538 
539             RunningTracker tracker = supplier.get();
540             if (tracker == null) {
541                 return null;
542             }
543 
544             mRunningTrackers.put(cuj, tracker);
545             if (mDebugOverlay != null) {
546                 mDebugOverlay.onTrackerAdded(cuj, tracker.mTracker.hashCode());
547             }
548 
549             return tracker;
550         }
551     }
552 
getTracker(@uj.CujType int cuj)553     private RunningTracker getTracker(@Cuj.CujType int cuj) {
554         synchronized (mLock) {
555             return mRunningTrackers.get(cuj);
556         }
557     }
558 
559     /**
560      * @return {@code true} if another tracker is current
561      */
562     @UiThread
removeTrackerIfCurrent(RunningTracker tracker, int reason)563     private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) {
564         return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason);
565     }
566 
567     /**
568      * @return {@code true} if another tracker is current
569      */
570     @UiThread
removeTrackerIfCurrent(@uj.CujType int cuj, FrameTracker tracker, int reason)571     private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) {
572         synchronized (mLock) {
573             RunningTracker running = mRunningTrackers.get(cuj);
574             if (running == null || running.mTracker != tracker) {
575                 return true;
576             }
577 
578             running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
579             mRunningTrackers.remove(cuj);
580             if (mDebugOverlay != null) {
581                 mDebugOverlay.onTrackerRemoved(cuj, reason, tracker.hashCode());
582             }
583             return false;
584         }
585     }
586 
587     @WorkerThread
588     @VisibleForTesting
updateProperties(DeviceConfig.Properties properties)589     public void updateProperties(DeviceConfig.Properties properties) {
590         for (String property : properties.getKeyset()) {
591             switch (property) {
592                 case SETTINGS_SAMPLING_INTERVAL_KEY ->
593                         mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
594                 case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY ->
595                         mTraceThresholdMissedFrames =
596                                 properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
597                 case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY ->
598                         mTraceThresholdFrameTimeMillis =
599                                 properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
600                 case SETTINGS_ENABLED_KEY ->
601                         mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
602                 case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
603                     // Never allow the debug overlay to be used on user builds
604                     if (Build.IS_USER) break;
605                     boolean debugOverlayEnabled = properties.getBoolean(property,
606                             DEFAULT_DEBUG_OVERLAY_ENABLED);
607                     synchronized (mLock) {
608                         if (debugOverlayEnabled && mDebugOverlay == null) {
609                             // Use the worker thread as the UI thread for the debug overlay:
610                             mDebugOverlay = new InteractionMonitorDebugOverlay(
611                                     mCurrentApplication, mWorker, mDebugBgColor, mDebugYOffset);
612                         } else if (!debugOverlayEnabled && mDebugOverlay != null) {
613                             mDebugOverlay.dispose();
614                             mDebugOverlay = null;
615                         }
616                     }
617                 }
618                 default -> Log.w(TAG, "Got a change event for an unknown property: "
619                         + property + " => " + properties.getString(property, ""));
620             }
621         }
622     }
623 
624     /**
625      * A helper method to translate interaction type to CUJ name.
626      *
627      * @param interactionType the interaction type defined in AtomsProto.java
628      * @return the name of the interaction type
629      * @deprecated use {@link Cuj#getNameOfInteraction(int)}
630      */
631     @Deprecated
getNameOfInteraction(int interactionType)632     public static String getNameOfInteraction(int interactionType) {
633         return Cuj.getNameOfInteraction(interactionType);
634     }
635 
636     /**
637      * A helper method to translate CUJ type to CUJ name.
638      *
639      * @param cujType the cuj type defined in this file
640      * @return the name of the cuj type
641      * @deprecated use {@link Cuj#getNameOfCuj(int)}
642      */
643     @Deprecated
getNameOfCuj(int cujType)644     public static String getNameOfCuj(int cujType) {
645         return Cuj.getNameOfCuj(cujType);
646     }
647 
648     /**
649      * Configures the debug overlay used for displaying interaction names on the screen while they
650      * occur.
651      *
652      * @param bgColor the background color of the box used to display the CUJ names
653      * @param yOffset number between 0 and 1 to indicate where the top of the box should be relative
654      *                to the height of the screen
655      */
configDebugOverlay(@olorInt int bgColor, double yOffset)656     public void configDebugOverlay(@ColorInt int bgColor, double yOffset) {
657         mDebugBgColor = bgColor;
658         mDebugYOffset = yOffset;
659     }
660 
postEventLogToWorkerThread(TimeFunction logFunction)661     private void postEventLogToWorkerThread(TimeFunction logFunction) {
662         final Instant now = Instant.now();
663         final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
664                 + now.getNano();
665         final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
666         final long realtimeNanos = SystemClock.uptimeNanos();
667 
668         mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos));
669     }
670 
671     private static class TrackerResult {
672         private boolean mResult;
673     }
674 
675     /**
676      * Configurations used while instrumenting the CUJ. <br/>
677      * <b>It may refer to an attached view, don't use static reference for any purpose.</b>
678      */
679     public static class Configuration {
680         private final View mView;
681         private final Context mContext;
682         private final long mTimeout;
683         private final String mTag;
684         private final String mSessionName;
685         private final boolean mSurfaceOnly;
686         private final SurfaceControl mSurfaceControl;
687         private final @Cuj.CujType int mCujType;
688         private final boolean mDeferMonitor;
689         private final Handler mHandler;
690 
691         /**
692          * A builder for building Configuration. {@link #setView(View)} is essential
693          * if {@link #setSurfaceOnly(boolean)} is not set, otherwise both
694          * {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)}
695          * are necessary<br/>
696          * <b>It may refer to an attached view, don't use static reference for any purpose.</b>
697          */
698         public static class Builder {
699             private View mAttrView = null;
700             private Context mAttrContext = null;
701             private long mAttrTimeout = DEFAULT_TIMEOUT_MS;
702             private String mAttrTag = "";
703             private boolean mAttrSurfaceOnly;
704             private SurfaceControl mAttrSurfaceControl;
705             private final @Cuj.CujType int mAttrCujType;
706             private boolean mAttrDeferMonitor = true;
707             private Handler mHandler = null;
708 
709             /**
710              * Creates a builder which instruments only surface.
711              * @param cuj The enum defined in {@link Cuj.CujType}.
712              * @param context context
713              * @param surfaceControl surface control
714              * @param uiThreadHandler UI thread for that surface
715              * @return builder
716              */
withSurface(@uj.CujType int cuj, @NonNull Context context, @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler)717             public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
718                     @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler) {
719                 return new Builder(cuj)
720                         .setContext(context)
721                         .setSurfaceControl(surfaceControl)
722                         .setSurfaceOnly(true)
723                         .setHandler(uiThreadHandler);
724             }
725 
726             /**
727              * Creates a builder which instruments both surface and view.
728              * @param cuj The enum defined in {@link Cuj.CujType}.
729              * @param view view
730              * @return builder
731              */
withView(@uj.CujType int cuj, @NonNull View view)732             public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) {
733                 return new Builder(cuj)
734                         .setView(view)
735                         .setContext(view.getContext());
736             }
737 
Builder(@uj.CujType int cuj)738             private Builder(@Cuj.CujType int cuj) {
739                 mAttrCujType = cuj;
740             }
741 
742             /**
743              * Specifies the UI thread handler. If not provided, the View's one will be used.
744              * If only a surface is provided without handler, the app main thread will be used.
745              *
746              * @param uiThreadHandler handler associated to the cuj UI thread
747              * @return builder
748              */
setHandler(Handler uiThreadHandler)749             public Builder setHandler(Handler uiThreadHandler) {
750                 mHandler = uiThreadHandler;
751                 return this;
752             }
753 
754             /**
755              * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
756              * @param view an attached view
757              * @return builder
758              */
setView(@onNull View view)759             private Builder setView(@NonNull View view) {
760                 mAttrView = view;
761                 return this;
762             }
763 
764             /**
765              * @param timeout duration to cancel the instrumentation in ms
766              * @return builder
767              */
setTimeout(long timeout)768             public Builder setTimeout(long timeout) {
769                 mAttrTimeout = timeout;
770                 return this;
771             }
772 
773             /**
774              * @param tag The postfix of the CUJ in the output trace.
775              *           It provides a brief description for the CUJ like the concrete class
776              *           who is dealing with the CUJ or the important state with the CUJ, etc.
777              * @return builder
778              */
setTag(@onNull String tag)779             public Builder setTag(@NonNull String tag) {
780                 mAttrTag = tag;
781                 return this;
782             }
783 
784             /**
785              * Indicates if only instrument with surface,
786              * if true, must also setup with {@link #setContext(Context)}
787              * and {@link #setSurfaceControl(SurfaceControl)}.
788              * @param surfaceOnly true if only instrument with surface, false otherwise
789              * @return builder Surface only builder.
790              */
setSurfaceOnly(boolean surfaceOnly)791             private Builder setSurfaceOnly(boolean surfaceOnly) {
792                 mAttrSurfaceOnly = surfaceOnly;
793                 return this;
794             }
795 
796             /**
797              * Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set.
798              */
setContext(Context context)799             private Builder setContext(Context context) {
800                 mAttrContext = context;
801                 return this;
802             }
803 
804             /**
805              * Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set.
806              */
setSurfaceControl(SurfaceControl surfaceControl)807             private Builder setSurfaceControl(SurfaceControl surfaceControl) {
808                 mAttrSurfaceControl = surfaceControl;
809                 return this;
810             }
811 
812             /**
813              * Indicates if the instrument should be deferred to the next frame.
814              * @param defer true if the instrument should be deferred to the next frame.
815              * @return builder
816              */
setDeferMonitorForAnimationStart(boolean defer)817             public Builder setDeferMonitorForAnimationStart(boolean defer) {
818                 mAttrDeferMonitor = defer;
819                 return this;
820             }
821 
822             /**
823              * Builds the {@link Configuration} instance
824              * @return the instance of {@link Configuration}
825              * @throws IllegalArgumentException if any invalid attribute is set
826              */
build()827             public Configuration build() throws IllegalArgumentException {
828                 return new Configuration(
829                         mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
830                         mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl,
831                         mAttrDeferMonitor, mHandler);
832             }
833         }
834 
Configuration(@uj.CujType int cuj, View view, @NonNull String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl, boolean deferMonitor, Handler handler)835         private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
836                 boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
837                 boolean deferMonitor, Handler handler) {
838             mCujType = cuj;
839             mTag = tag;
840             mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
841             mTimeout = timeout;
842             mView = view;
843             mSurfaceOnly = surfaceOnly;
844             mContext = context != null
845                     ? context
846                     : (view != null ? view.getContext().getApplicationContext() : null);
847             mSurfaceControl = surfaceControl;
848             mDeferMonitor = deferMonitor;
849             if (handler != null) {
850                 mHandler = handler;
851             } else if (mSurfaceOnly) {
852                 Log.w(TAG, "No UIThread provided for " + mSessionName
853                         + " (surface only). Defaulting to app main thread.");
854                 mHandler = mContext.getMainThreadHandler();
855             } else {
856                 mHandler = mView.getHandler();
857             }
858             validate();
859         }
860 
861         @VisibleForTesting
generateSessionName( @onNull String cujName, @NonNull String cujPostfix)862         public static String generateSessionName(
863                 @NonNull String cujName, @NonNull String cujPostfix) {
864             final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
865             if (hasPostfix) {
866                 final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
867                 if (cujPostfix.length() > remaining) {
868                     cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
869                 }
870             }
871             // The max length of the whole string should be:
872             // 105 with postfix, 83 without postfix
873             return hasPostfix
874                     ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
875                     : TextUtils.formatSimple("J<%s>", cujName);
876         }
877 
validate()878         private void validate() {
879             boolean shouldThrow = false;
880             final StringBuilder msg = new StringBuilder();
881 
882             if (mTag == null) {
883                 shouldThrow = true;
884                 msg.append("Invalid tag; ");
885             }
886             if (mTimeout < 0) {
887                 shouldThrow = true;
888                 msg.append("Invalid timeout value; ");
889             }
890             if (mSurfaceOnly) {
891                 if (mContext == null) {
892                     shouldThrow = true;
893                     msg.append("Must pass in a context if only instrument surface; ");
894                 }
895                 if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
896                     shouldThrow = true;
897                     msg.append("Must pass in a valid surface control if only instrument surface; ");
898                 }
899                 if (mHandler == null) {
900                     shouldThrow = true;
901                     msg.append(
902                             "Must pass a UI thread handler when only a surface control is "
903                                     + "provided.");
904                 }
905             } else {
906                 if (!hasValidView()) {
907                     shouldThrow = true;
908                     boolean attached = false;
909                     boolean hasViewRoot = false;
910                     boolean hasRenderer = false;
911                     if (mView != null) {
912                         attached = mView.isAttachedToWindow();
913                         hasViewRoot = mView.getViewRootImpl() != null;
914                         hasRenderer = mView.getThreadedRenderer() != null;
915                     }
916                     String err = "invalid view: view=" + mView + ", attached=" + attached
917                             + ", hasViewRoot=" + hasViewRoot + ", hasRenderer=" + hasRenderer;
918                     msg.append(err);
919                 }
920             }
921             if (shouldThrow) {
922                 throw new IllegalArgumentException(msg.toString());
923             }
924         }
925 
hasValidView()926         boolean hasValidView() {
927             return mSurfaceOnly
928                     || (mView != null && mView.isAttachedToWindow()
929                     && mView.getViewRootImpl() != null && mView.getThreadedRenderer() != null);
930         }
931 
932         /**
933          * @return true if only instrumenting surface, false otherwise
934          */
isSurfaceOnly()935         public boolean isSurfaceOnly() {
936             return mSurfaceOnly;
937         }
938 
939         /**
940          * @return the surafce control which is instrumenting
941          */
getSurfaceControl()942         public SurfaceControl getSurfaceControl() {
943             return mSurfaceControl;
944         }
945 
946         /**
947          * @return a view which is attached to the view tree.
948          */
949         @VisibleForTesting
getView()950         public View getView() {
951             return mView;
952         }
953 
954         /**
955          * @return true if the monitoring should be deferred to the next frame, false otherwise.
956          */
shouldDeferMonitor()957         public boolean shouldDeferMonitor() {
958             return mDeferMonitor;
959         }
960 
961         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getHandler()962         public Handler getHandler() {
963             return mHandler;
964         }
965 
966         /**
967          * @return the ID of the display this interaction in on.
968          */
969         @VisibleForTesting
getDisplayId()970         public int getDisplayId() {
971             return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId();
972         }
973 
getSessionName()974         public String getSessionName() {
975             return mSessionName;
976         }
977 
getStatsdInteractionType()978         public int getStatsdInteractionType() {
979             return Cuj.getStatsdInteractionType(mCujType);
980         }
981 
982         /** Describes whether the measurement from this session should be written to statsd. */
logToStatsd()983         public boolean logToStatsd() {
984             return Cuj.logToStatsd(mCujType);
985         }
986 
getPerfettoTrigger()987         public String getPerfettoTrigger() {
988             return TextUtils.formatSimple(
989                     "com.android.telemetry.interaction-jank-monitor-%d", mCujType);
990         }
991 
getCujType()992         public @Cuj.CujType int getCujType() {
993             return mCujType;
994         }
995     }
996 
997     @FunctionalInterface
998     private interface TimeFunction {
invoke(long unixNanos, long elapsedNanos, long realtimeNanos)999         void invoke(long unixNanos, long elapsedNanos, long realtimeNanos);
1000     }
1001 
1002     static class RunningTracker {
1003         public final Configuration mConfig;
1004         public final FrameTracker mTracker;
1005         public final Runnable mTimeoutAction;
1006 
RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction)1007         RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) {
1008             this.mConfig = config;
1009             this.mTracker = tracker;
1010             this.mTimeoutAction = timeoutAction;
1011         }
1012     }
1013 }
1014