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