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