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