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.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
20 
21 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
22 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN;
23 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
24 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
25 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
26 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
27 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
28 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
29 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
30 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
31 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
32 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
33 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
34 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
35 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
36 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
37 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
38 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
39 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
40 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
41 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
42 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
43 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
44 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
45 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
46 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
47 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
48 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
49 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
50 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
51 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
52 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
53 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
54 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
55 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
56 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
57 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
58 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
59 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
60 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
61 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
62 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
63 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
64 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
65 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
66 
67 import android.annotation.IntDef;
68 import android.annotation.NonNull;
69 import android.content.Context;
70 import android.content.Intent;
71 import android.os.Build;
72 import android.os.HandlerExecutor;
73 import android.os.HandlerThread;
74 import android.os.SystemProperties;
75 import android.provider.DeviceConfig;
76 import android.text.TextUtils;
77 import android.util.Log;
78 import android.util.SparseArray;
79 import android.view.Choreographer;
80 import android.view.SurfaceControl;
81 import android.view.View;
82 
83 import com.android.internal.annotations.VisibleForTesting;
84 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
85 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
86 import com.android.internal.jank.FrameTracker.FrameTrackerListener;
87 import com.android.internal.jank.FrameTracker.Reasons;
88 import com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
89 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
90 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
91 import com.android.internal.util.PerfettoTrigger;
92 
93 import java.lang.annotation.Retention;
94 import java.lang.annotation.RetentionPolicy;
95 import java.util.Locale;
96 import java.util.concurrent.ThreadLocalRandom;
97 import java.util.concurrent.TimeUnit;
98 
99 /**
100  * This class let users to begin and end the always on tracing mechanism.
101  *
102  * Enabling for local development:
103  *
104  * adb shell device_config put interaction_jank_monitor enabled true
105  * adb shell device_config put interaction_jank_monitor sampling_interval 1
106  *
107  * @hide
108  */
109 public class InteractionJankMonitor {
110     private static final String TAG = InteractionJankMonitor.class.getSimpleName();
111     private static final boolean DEBUG = false;
112     private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
113 
114     private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
115     private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
116     private static final String SETTINGS_ENABLED_KEY = "enabled";
117     private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
118     private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
119             "trace_threshold_missed_frames";
120     private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
121             "trace_threshold_frame_time_millis";
122     /** Default to being enabled on debug builds. */
123     private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
124     /** Default to collecting data for all CUJs. */
125     private static final int DEFAULT_SAMPLING_INTERVAL = 1;
126     /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
127     private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
128     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
129 
130     public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN";
131     public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
132     public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
133     public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED";
134     public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME";
135     public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP";
136     @VisibleForTesting
137     public static final String PROP_NOTIFY_CUJ_EVENT = "debug.jank.notify_cuj_events";
138 
139     // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
140     public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
141     public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1;
142     public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
143     public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
144     public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
145     public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
146     public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
147     public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
148     public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
149     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
150     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
151     public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
152     public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
153     public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
154     public static final int CUJ_NOTIFICATION_ADD = 14;
155     public static final int CUJ_NOTIFICATION_REMOVE = 15;
156     public static final int CUJ_NOTIFICATION_APP_START = 16;
157     public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
158     public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
159     public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
160     public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
161     public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
162     public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
163     public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
164     public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
165     public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
166     public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
167     public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
168     public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
169     public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
170     public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
171     public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
172     public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
173     public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
174     public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
175     public static final int CUJ_PIP_TRANSITION = 35;
176     public static final int CUJ_WALLPAPER_TRANSITION = 36;
177     public static final int CUJ_USER_SWITCH = 37;
178     public static final int CUJ_SPLASHSCREEN_AVD = 38;
179     public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
180 
181     private static final int NO_STATSD_LOGGING = -1;
182 
183     // Used to convert CujType to InteractionType enum value for statsd logging.
184     // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
185     @VisibleForTesting
186     public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
187             // This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
188             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
189             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK,
190             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
191             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
192             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
193             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE,
194             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE,
195             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS,
196             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON,
197             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME,
198             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP,
199             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH,
200             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR,
201             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR,
202             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD,
203             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE,
204             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH,
205             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR,
206             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR,
207             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR,
208             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR,
209             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR,
210             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR,
211             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD,
212             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD,
213             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS,
214             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL,
215             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET,
216             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL,
217             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION,
218             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
219             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
220             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE,
221             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
222             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
223             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION,
224             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION,
225             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH,
226             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD,
227             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM,
228     };
229 
230     private static volatile InteractionJankMonitor sInstance;
231 
232     private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
233             this::updateProperties;
234 
235     private final FrameMetricsWrapper mMetrics;
236     private final SparseArray<FrameTracker> mRunningTrackers;
237     private final SparseArray<Runnable> mTimeoutActions;
238     private final HandlerThread mWorker;
239     private final Object mLock = new Object();
240 
241     private boolean mEnabled = DEFAULT_ENABLED;
242     private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
243     private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
244     private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
245 
246     /** @hide */
247     @IntDef({
248             CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
249             CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
250             CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
251             CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
252             CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
253             CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
254             CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
255             CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
256             CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
257             CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
258             CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
259             CUJ_LAUNCHER_QUICK_SWITCH,
260             CUJ_NOTIFICATION_HEADS_UP_APPEAR,
261             CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
262             CUJ_NOTIFICATION_ADD,
263             CUJ_NOTIFICATION_REMOVE,
264             CUJ_NOTIFICATION_APP_START,
265             CUJ_LOCKSCREEN_PASSWORD_APPEAR,
266             CUJ_LOCKSCREEN_PATTERN_APPEAR,
267             CUJ_LOCKSCREEN_PIN_APPEAR,
268             CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
269             CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
270             CUJ_LOCKSCREEN_PIN_DISAPPEAR,
271             CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
272             CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
273             CUJ_LAUNCHER_OPEN_ALL_APPS,
274             CUJ_LAUNCHER_ALL_APPS_SCROLL,
275             CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
276             CUJ_SETTINGS_PAGE_SCROLL,
277             CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
278             CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
279             CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
280             CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
281             CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
282             CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
283             CUJ_PIP_TRANSITION,
284             CUJ_WALLPAPER_TRANSITION,
285             CUJ_USER_SWITCH,
286             CUJ_SPLASHSCREEN_AVD,
287             CUJ_SPLASHSCREEN_EXIT_ANIM,
288     })
289     @Retention(RetentionPolicy.SOURCE)
290     public @interface CujType {
291     }
292 
293     /**
294      * Get the singleton of InteractionJankMonitor.
295      *
296      * @return instance of InteractionJankMonitor
297      */
getInstance()298     public static InteractionJankMonitor getInstance() {
299         // Use DCL here since this method might be invoked very often.
300         if (sInstance == null) {
301             synchronized (InteractionJankMonitor.class) {
302                 if (sInstance == null) {
303                     sInstance = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
304                 }
305             }
306         }
307         return sInstance;
308     }
309 
310     /**
311      * This constructor should be only public to tests.
312      *
313      * @param worker the worker thread for the callbacks
314      */
315     @VisibleForTesting
InteractionJankMonitor(@onNull HandlerThread worker)316     public InteractionJankMonitor(@NonNull HandlerThread worker) {
317         mRunningTrackers = new SparseArray<>();
318         mTimeoutActions = new SparseArray<>();
319         mWorker = worker;
320         mMetrics = new FrameMetricsWrapper();
321         mWorker.start();
322         mEnabled = DEFAULT_ENABLED;
323         mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
324 
325         // Post initialization to the background in case we're running on the main
326         // thread.
327         mWorker.getThreadHandler().post(
328                 () -> mPropertiesChangedListener.onPropertiesChanged(
329                         DeviceConfig.getProperties(
330                                 DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
331         DeviceConfig.addOnPropertiesChangedListener(
332                 DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
333                 new HandlerExecutor(mWorker.getThreadHandler()),
334                 mPropertiesChangedListener);
335     }
336 
getLock()337     Object getLock() {
338         return mLock;
339     }
340 
341     /**
342      * Creates a {@link FrameTracker} instance.
343      *
344      * @param config the config used in instrumenting
345      * @param session the session associates with this tracker
346      * @return instance of the FrameTracker
347      */
348     @VisibleForTesting
createFrameTracker(Configuration config, Session session)349     public FrameTracker createFrameTracker(Configuration config, Session session) {
350         final View view = config.mView;
351         final ThreadedRendererWrapper threadedRenderer =
352                 view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
353         final ViewRootWrapper viewRoot =
354                 view == null ? null : new ViewRootWrapper(view.getViewRootImpl());
355 
356         final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
357         final ChoreographerWrapper choreographer =
358                 new ChoreographerWrapper(Choreographer.getInstance());
359 
360         synchronized (mLock) {
361             FrameTrackerListener eventsListener =
362                     (s, act) -> handleCujEvents(config.getContext(), act, s);
363             return new FrameTracker(session, mWorker.getThreadHandler(),
364                     threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics,
365                     mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
366                     eventsListener, config);
367         }
368     }
369 
handleCujEvents(Context context, String action, Session session)370     private void handleCujEvents(Context context, String action, Session session) {
371         // Clear the running and timeout tasks if the end / cancel was fired within the tracker.
372         // Or we might have memory leaks.
373         if (needRemoveTasks(action, session)) {
374             removeTimeout(session.getCuj());
375             removeTracker(session.getCuj());
376         }
377 
378         // Notify the receivers if necessary.
379         if (session.shouldNotify()) {
380             if (context != null) {
381                 notifyEvents(context, action, session);
382             } else {
383                 throw new IllegalArgumentException(
384                         "Can't notify cuj events due to lack of context: cuj="
385                         + session.getName() + ", action=" + action);
386             }
387         }
388     }
389 
needRemoveTasks(String action, Session session)390     private boolean needRemoveTasks(String action, Session session) {
391         final boolean badEnd = action.equals(ACTION_SESSION_END)
392                 && session.getReason() != REASON_END_NORMAL;
393         final boolean badCancel = action.equals(ACTION_SESSION_CANCEL)
394                 && !(session.getReason() == REASON_CANCEL_NORMAL
395                 || session.getReason() == REASON_CANCEL_TIMEOUT);
396         return badEnd || badCancel;
397     }
398 
399     /**
400      * Notifies who may interest in some CUJ events.
401      */
402     @VisibleForTesting
notifyEvents(Context context, String action, Session session)403     public void notifyEvents(Context context, String action, Session session) {
404         if (action.equals(ACTION_SESSION_CANCEL)
405                 && session.getReason() == REASON_CANCEL_NOT_BEGUN) {
406             return;
407         }
408         Intent intent = new Intent(action);
409         intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj()));
410         intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp());
411         intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY);
412         context.sendBroadcast(intent);
413     }
414 
removeTimeout(@ujType int cujType)415     private void removeTimeout(@CujType int cujType) {
416         synchronized (mLock) {
417             Runnable timeout = mTimeoutActions.get(cujType);
418             if (timeout != null) {
419                 mWorker.getThreadHandler().removeCallbacks(timeout);
420                 mTimeoutActions.remove(cujType);
421             }
422         }
423     }
424 
425     /**
426      * Begins a trace session.
427      *
428      * @param v an attached view.
429      * @param cujType the specific {@link InteractionJankMonitor.CujType}.
430      * @return boolean true if the tracker is started successfully, false otherwise.
431      */
begin(View v, @CujType int cujType)432     public boolean begin(View v, @CujType int cujType) {
433         try {
434             return beginInternal(
435                     Configuration.Builder.withView(cujType, v)
436                             .build());
437         } catch (IllegalArgumentException ex) {
438             Log.d(TAG, "Build configuration failed!", ex);
439             return false;
440         }
441     }
442 
443     /**
444      * Begins a trace session.
445      *
446      * @param builder the builder of the configurations for instrumenting the CUJ.
447      * @return boolean true if the tracker is started successfully, false otherwise.
448      */
begin(@onNull Configuration.Builder builder)449     public boolean begin(@NonNull Configuration.Builder builder) {
450         try {
451             return beginInternal(builder.build());
452         } catch (IllegalArgumentException ex) {
453             Log.d(TAG, "Build configuration failed!", ex);
454             return false;
455         }
456     }
457 
beginInternal(@onNull Configuration conf)458     private boolean beginInternal(@NonNull Configuration conf) {
459         synchronized (mLock) {
460             int cujType = conf.mCujType;
461             if (!shouldMonitor(cujType)) return false;
462             FrameTracker tracker = getTracker(cujType);
463             // Skip subsequent calls if we already have an ongoing tracing.
464             if (tracker != null) return false;
465 
466             // begin a new trace session.
467             tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
468             mRunningTrackers.put(cujType, tracker);
469             tracker.begin();
470 
471             // Cancel the trace if we don't get an end() call in specified duration.
472             scheduleTimeoutAction(
473                     cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
474             return true;
475         }
476     }
477 
478     /**
479      * Check if the monitoring is enabled and if it should be sampled.
480      */
481     @SuppressWarnings("RandomModInteger")
482     @VisibleForTesting
shouldMonitor(@ujType int cujType)483     public boolean shouldMonitor(@CujType int cujType) {
484         boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
485         if (!mEnabled || !shouldSample) {
486             if (DEBUG) {
487                 Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType)
488                         + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED
489                         + ", sample=" + shouldSample + ", interval=" + mSamplingInterval);
490             }
491             return false;
492         }
493         return true;
494     }
495 
496     /**
497      * Schedules a timeout action.
498      * @param cuj cuj type
499      * @param timeout duration to timeout
500      * @param action action once timeout
501      */
502     @VisibleForTesting
scheduleTimeoutAction(@ujType int cuj, long timeout, Runnable action)503     public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
504         mTimeoutActions.put(cuj, action);
505         mWorker.getThreadHandler().postDelayed(action, timeout);
506     }
507 
508     /**
509      * Ends a trace session.
510      *
511      * @param cujType the specific {@link InteractionJankMonitor.CujType}.
512      * @return boolean true if the tracker is ended successfully, false otherwise.
513      */
end(@ujType int cujType)514     public boolean end(@CujType int cujType) {
515         synchronized (mLock) {
516             // remove the timeout action first.
517             removeTimeout(cujType);
518             FrameTracker tracker = getTracker(cujType);
519             // Skip this call since we haven't started a trace yet.
520             if (tracker == null) return false;
521             // if the end call doesn't return true, another thread is handling end of the cuj.
522             if (tracker.end(REASON_END_NORMAL)) {
523                 removeTracker(cujType);
524             }
525             return true;
526         }
527     }
528 
529     /**
530      * Cancels the trace session.
531      *
532      * @return boolean true if the tracker is cancelled successfully, false otherwise.
533      */
cancel(@ujType int cujType)534     public boolean cancel(@CujType int cujType) {
535         return cancel(cujType, REASON_CANCEL_NORMAL);
536     }
537 
538     /**
539      * Cancels the trace session.
540      *
541      * @return boolean true if the tracker is cancelled successfully, false otherwise.
542      */
543     @VisibleForTesting
cancel(@ujType int cujType, @Reasons int reason)544     public boolean cancel(@CujType int cujType, @Reasons int reason) {
545         synchronized (mLock) {
546             // remove the timeout action first.
547             removeTimeout(cujType);
548             FrameTracker tracker = getTracker(cujType);
549             // Skip this call since we haven't started a trace yet.
550             if (tracker == null) return false;
551             // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
552             if (tracker.cancel(reason)) {
553                 removeTracker(cujType);
554             }
555             return true;
556         }
557     }
558 
getTracker(@ujType int cuj)559     private FrameTracker getTracker(@CujType int cuj) {
560         return mRunningTrackers.get(cuj);
561     }
562 
removeTracker(@ujType int cuj)563     private void removeTracker(@CujType int cuj) {
564         mRunningTrackers.remove(cuj);
565     }
566 
updateProperties(DeviceConfig.Properties properties)567     private void updateProperties(DeviceConfig.Properties properties) {
568         synchronized (mLock) {
569             mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
570                     DEFAULT_SAMPLING_INTERVAL);
571             mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
572             mTraceThresholdMissedFrames = properties.getInt(SETTINGS_THRESHOLD_MISSED_FRAMES_KEY,
573                     DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
574             mTraceThresholdFrameTimeMillis = properties.getInt(
575                     SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
576                     DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
577         }
578     }
579 
580     @VisibleForTesting
getPropertiesChangedListener()581     public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() {
582         return mPropertiesChangedListener;
583     }
584 
585     /**
586      * Triggers the perfetto daemon to collect and upload data.
587      */
588     @VisibleForTesting
trigger(Session session)589     public void trigger(Session session) {
590         mWorker.getThreadHandler().post(
591                 () -> PerfettoTrigger.trigger(session.getPerfettoTrigger()));
592     }
593 
594     /**
595      * A helper method to translate interaction type to CUJ name.
596      *
597      * @param interactionType the interaction type defined in AtomsProto.java
598      * @return the name of the interaction type
599      */
getNameOfInteraction(int interactionType)600     public static String getNameOfInteraction(int interactionType) {
601         // There is an offset amount of 1 between cujType and interactionType.
602         return getNameOfCuj(interactionType - 1);
603     }
604 
605     /**
606      * A helper method to translate CUJ type to CUJ name.
607      *
608      * @param cujType the cuj type defined in this file
609      * @return the name of the cuj type
610      */
getNameOfCuj(int cujType)611     public static String getNameOfCuj(int cujType) {
612         switch (cujType) {
613             case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
614                 return "SHADE_EXPAND_COLLAPSE";
615             case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK:
616                 return "SHADE_EXPAND_COLLAPSE_LOCK";
617             case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
618                 return "SHADE_SCROLL_FLING";
619             case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
620                 return "SHADE_ROW_EXPAND";
621             case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
622                 return "SHADE_ROW_SWIPE";
623             case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
624                 return "SHADE_QS_EXPAND_COLLAPSE";
625             case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
626                 return "SHADE_QS_SCROLL_SWIPE";
627             case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
628                 return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
629             case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
630                 return "LAUNCHER_APP_LAUNCH_FROM_ICON";
631             case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
632                 return "LAUNCHER_APP_CLOSE_TO_HOME";
633             case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
634                 return "LAUNCHER_APP_CLOSE_TO_PIP";
635             case CUJ_LAUNCHER_QUICK_SWITCH:
636                 return "LAUNCHER_QUICK_SWITCH";
637             case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
638                 return "NOTIFICATION_HEADS_UP_APPEAR";
639             case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
640                 return "NOTIFICATION_HEADS_UP_DISAPPEAR";
641             case CUJ_NOTIFICATION_ADD:
642                 return "NOTIFICATION_ADD";
643             case CUJ_NOTIFICATION_REMOVE:
644                 return "NOTIFICATION_REMOVE";
645             case CUJ_NOTIFICATION_APP_START:
646                 return "NOTIFICATION_APP_START";
647             case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
648                 return "LOCKSCREEN_PASSWORD_APPEAR";
649             case CUJ_LOCKSCREEN_PATTERN_APPEAR:
650                 return "LOCKSCREEN_PATTERN_APPEAR";
651             case CUJ_LOCKSCREEN_PIN_APPEAR:
652                 return "LOCKSCREEN_PIN_APPEAR";
653             case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
654                 return "LOCKSCREEN_PASSWORD_DISAPPEAR";
655             case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
656                 return "LOCKSCREEN_PATTERN_DISAPPEAR";
657             case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
658                 return "LOCKSCREEN_PIN_DISAPPEAR";
659             case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
660                 return "LOCKSCREEN_TRANSITION_FROM_AOD";
661             case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
662                 return "LOCKSCREEN_TRANSITION_TO_AOD";
663             case CUJ_LAUNCHER_OPEN_ALL_APPS :
664                 return "LAUNCHER_OPEN_ALL_APPS";
665             case CUJ_LAUNCHER_ALL_APPS_SCROLL:
666                 return "LAUNCHER_ALL_APPS_SCROLL";
667             case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
668                 return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
669             case CUJ_SETTINGS_PAGE_SCROLL:
670                 return "SETTINGS_PAGE_SCROLL";
671             case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
672                 return "LOCKSCREEN_UNLOCK_ANIMATION";
673             case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
674                 return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
675             case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
676                 return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
677             case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
678                 return "SHADE_APP_LAUNCH_FROM_QS_TILE";
679             case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
680                 return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
681             case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
682                 return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
683             case CUJ_PIP_TRANSITION:
684                 return "PIP_TRANSITION";
685             case CUJ_WALLPAPER_TRANSITION:
686                 return "WALLPAPER_TRANSITION";
687             case CUJ_USER_SWITCH:
688                 return "USER_SWITCH";
689             case CUJ_SPLASHSCREEN_AVD:
690                 return "SPLASHSCREEN_AVD";
691             case CUJ_SPLASHSCREEN_EXIT_ANIM:
692                 return "SPLASHSCREEN_EXIT_ANIM";
693         }
694         return "UNKNOWN";
695     }
696 
697     /**
698      * Configurations used while instrumenting the CUJ. <br/>
699      * <b>It may refer to an attached view, don't use static reference for any purpose.</b>
700      */
701     public static class Configuration {
702         private final View mView;
703         private final Context mContext;
704         private final long mTimeout;
705         private final String mTag;
706         private final boolean mSurfaceOnly;
707         private final SurfaceControl mSurfaceControl;
708         private final @CujType int mCujType;
709 
710         /**
711          * A builder for building Configuration. {@link #setView(View)} is essential
712          * if {@link #setSurfaceOnly(boolean)} is not set, otherwise both
713          * {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)}
714          * are necessary<br/>
715          * <b>It may refer to an attached view, don't use static reference for any purpose.</b>
716          */
717         public static class Builder {
718             private View mAttrView = null;
719             private Context mAttrContext = null;
720             private long mAttrTimeout = DEFAULT_TIMEOUT_MS;
721             private String mAttrTag = "";
722             private boolean mAttrSurfaceOnly;
723             private SurfaceControl mAttrSurfaceControl;
724             private @CujType int mAttrCujType;
725 
726             /**
727              * Creates a builder which instruments only surface.
728              * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
729              * @param context context
730              * @param surfaceControl surface control
731              * @return builder
732              */
withSurface(@ujType int cuj, @NonNull Context context, @NonNull SurfaceControl surfaceControl)733             public static Builder withSurface(@CujType int cuj, @NonNull Context context,
734                     @NonNull SurfaceControl surfaceControl) {
735                 return new Builder(cuj)
736                         .setContext(context)
737                         .setSurfaceControl(surfaceControl)
738                         .setSurfaceOnly(true);
739             }
740 
741             /**
742              * Creates a builder which instruments both surface and view.
743              * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
744              * @param view view
745              * @return builder
746              */
withView(@ujType int cuj, @NonNull View view)747             public static Builder withView(@CujType int cuj, @NonNull View view) {
748                 return new Builder(cuj).setView(view)
749                         .setContext(view.getContext());
750             }
751 
Builder(@ujType int cuj)752             private Builder(@CujType int cuj) {
753                 mAttrCujType = cuj;
754             }
755 
756             /**
757              * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
758              * @param view an attached view
759              * @return builder
760              */
setView(@onNull View view)761             private Builder setView(@NonNull View view) {
762                 mAttrView = view;
763                 return this;
764             }
765 
766             /**
767              * @param timeout duration to cancel the instrumentation in ms
768              * @return builder
769              */
setTimeout(long timeout)770             public Builder setTimeout(long timeout) {
771                 mAttrTimeout = timeout;
772                 return this;
773             }
774 
775             /**
776              * @param tag The postfix of the CUJ in the output trace.
777              *           It provides a brief description for the CUJ like the concrete class
778              *           who is dealing with the CUJ or the important state with the CUJ, etc.
779              * @return builder
780              */
setTag(@onNull String tag)781             public Builder setTag(@NonNull String tag) {
782                 mAttrTag = tag;
783                 return this;
784             }
785 
786             /**
787              * Indicates if only instrument with surface,
788              * if true, must also setup with {@link #setContext(Context)}
789              * and {@link #setSurfaceControl(SurfaceControl)}.
790              * @param surfaceOnly true if only instrument with surface, false otherwise
791              * @return builder Surface only builder.
792              */
setSurfaceOnly(boolean surfaceOnly)793             private Builder setSurfaceOnly(boolean surfaceOnly) {
794                 mAttrSurfaceOnly = surfaceOnly;
795                 return this;
796             }
797 
798             /**
799              * Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set.
800              */
setContext(Context context)801             private Builder setContext(Context context) {
802                 mAttrContext = context;
803                 return this;
804             }
805 
806             /**
807              * Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set.
808              */
setSurfaceControl(SurfaceControl surfaceControl)809             private Builder setSurfaceControl(SurfaceControl surfaceControl) {
810                 mAttrSurfaceControl = surfaceControl;
811                 return this;
812             }
813 
814             /**
815              * Builds the {@link Configuration} instance
816              * @return the instance of {@link Configuration}
817              * @throws IllegalArgumentException if any invalid attribute is set
818              */
build()819             public Configuration build() throws IllegalArgumentException {
820                 return new Configuration(
821                         mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
822                         mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl);
823             }
824         }
825 
Configuration(@ujType int cuj, View view, String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl)826         private Configuration(@CujType int cuj, View view, String tag, long timeout,
827                 boolean surfaceOnly, Context context, SurfaceControl surfaceControl) {
828             mCujType = cuj;
829             mTag = tag;
830             mTimeout = timeout;
831             mView = view;
832             mSurfaceOnly = surfaceOnly;
833             mContext = context != null
834                     ? context
835                     : (view != null ? view.getContext().getApplicationContext() : null);
836             mSurfaceControl = surfaceControl;
837             validate();
838         }
839 
validate()840         private void validate() {
841             boolean shouldThrow = false;
842             final StringBuilder msg = new StringBuilder();
843 
844             if (mTag == null) {
845                 shouldThrow = true;
846                 msg.append("Invalid tag; ");
847             }
848             if (mTimeout < 0) {
849                 shouldThrow = true;
850                 msg.append("Invalid timeout value; ");
851             }
852             if (mSurfaceOnly) {
853                 if (mContext == null) {
854                     shouldThrow = true;
855                     msg.append("Must pass in a context if only instrument surface; ");
856                 }
857                 if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
858                     shouldThrow = true;
859                     msg.append("Must pass in a valid surface control if only instrument surface; ");
860                 }
861             } else {
862                 if (mView == null || !mView.isAttachedToWindow()) {
863                     shouldThrow = true;
864                     msg.append("Null view or unattached view while instrumenting view; ");
865                 }
866             }
867             if (shouldThrow) {
868                 throw new IllegalArgumentException(msg.toString());
869             }
870         }
871 
872         /**
873          * @return true if only instrumenting surface, false otherwise
874          */
isSurfaceOnly()875         public boolean isSurfaceOnly() {
876             return mSurfaceOnly;
877         }
878 
879         /**
880          * @return the surafce control which is instrumenting
881          */
getSurfaceControl()882         public SurfaceControl getSurfaceControl() {
883             return mSurfaceControl;
884         }
885 
getView()886         View getView() {
887             return mView;
888         }
889 
getContext()890         Context getContext() {
891             return mContext;
892         }
893     }
894 
895     /**
896      * A class to represent a session.
897      */
898     public static class Session {
899         @CujType
900         private final int mCujType;
901         private final long mTimeStamp;
902         @Reasons
903         private int mReason = REASON_END_UNKNOWN;
904         private final boolean mShouldNotify;
905         private final String mName;
906 
Session(@ujType int cujType, @NonNull String postfix)907         public Session(@CujType int cujType, @NonNull String postfix) {
908             mCujType = cujType;
909             mTimeStamp = System.nanoTime();
910             mShouldNotify = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false);
911             mName = TextUtils.isEmpty(postfix)
912                     ? String.format("J<%s>", getNameOfCuj(mCujType))
913                     : String.format("J<%s::%s>", getNameOfCuj(mCujType), postfix);
914         }
915 
916         @CujType
getCuj()917         public int getCuj() {
918             return mCujType;
919         }
920 
getStatsdInteractionType()921         public int getStatsdInteractionType() {
922             return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType];
923         }
924 
925         /** Describes whether the measurement from this session should be written to statsd. */
logToStatsd()926         public boolean logToStatsd() {
927             return getStatsdInteractionType() != NO_STATSD_LOGGING;
928         }
929 
getPerfettoTrigger()930         public String getPerfettoTrigger() {
931             return String.format(Locale.US, "com.android.telemetry.interaction-jank-monitor-%d",
932                     mCujType);
933         }
934 
getName()935         public String getName() {
936             return mName;
937         }
938 
getTimeStamp()939         public long getTimeStamp() {
940             return mTimeStamp;
941         }
942 
setReason(@easons int reason)943         public void setReason(@Reasons int reason) {
944             mReason = reason;
945         }
946 
getReason()947         public @Reasons int getReason() {
948             return mReason;
949         }
950 
951         /** Determines if should notify the receivers of cuj events */
shouldNotify()952         public boolean shouldNotify() {
953             return mShouldNotify;
954         }
955     }
956 }
957