1 /* 2 * Copyright (C) 2024 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 android.app.jank; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.util.Log; 24 import android.view.AttachedSurfaceControl; 25 import android.view.Choreographer; 26 import android.view.SurfaceControl; 27 import android.view.View; 28 import android.view.ViewRootImpl; 29 import android.view.ViewTreeObserver; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 import java.util.List; 36 37 /** 38 * This class is responsible for registering callbacks that will receive JankData batches. 39 * It handles managing the background thread that JankData will be processed on. As well as acting 40 * as an intermediary between widgets and the state tracker, routing state changes to the tracker. 41 * 42 * @hide 43 */ 44 @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) 45 public class JankTracker { 46 private static final boolean DEBUG = false; 47 private static final String DEBUG_KEY = "JANKTRACKER"; 48 // How long to delay the JankData listener registration. 49 //TODO b/394956095 see if this can be reduced or eliminated. 50 private static final int REGISTRATION_DELAY_MS = 1000; 51 // Tracks states reported by widgets. 52 private StateTracker mStateTracker; 53 // Processes JankData batches and associates frames to widget states. 54 private JankDataProcessor mJankDataProcessor; 55 56 // Background thread responsible for processing JankData batches. 57 private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker"); 58 private Handler mHandler = null; 59 60 // Handle to a registered OnJankData listener. 61 private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration; 62 63 // The interface to the windowing system that enables us to register for JankData. 64 private AttachedSurfaceControl mSurfaceControl; 65 // Name of the activity that is currently tracking Jank metrics. 66 private String mActivityName; 67 // The apps uid. 68 private int mAppUid; 69 // View that gives us access to ViewTreeObserver. 70 private View mDecorView; 71 72 /** 73 * Set by the activity to enable or disable jank tracking. Activities may disable tracking if 74 * they are paused or not enable tracking if they are not visible or if the app category is not 75 * set. 76 */ 77 private boolean mTrackingEnabled = false; 78 /** 79 * Set to true once listeners are registered and JankData will start to be received. Both 80 * mTrackingEnabled and mListenersRegistered need to be true for JankData to be processed. 81 */ 82 private boolean mListenersRegistered = false; 83 84 @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API) 85 private final SurfaceControl.OnJankDataListener mJankDataListener = 86 new SurfaceControl.OnJankDataListener() { 87 @Override 88 public void onJankDataAvailable( 89 @androidx.annotation.NonNull List<SurfaceControl.JankData> jankData) { 90 if (mJankDataProcessor == null) return; 91 mJankDataProcessor.processJankData(jankData, mActivityName, mAppUid); 92 } 93 }; 94 95 private final ViewTreeObserver.OnWindowAttachListener mOnWindowAttachListener = 96 new ViewTreeObserver.OnWindowAttachListener() { 97 @Override 98 public void onWindowAttached() { 99 getHandler().postDelayed(new Runnable() { 100 @Override 101 public void run() { 102 mDecorView.getViewTreeObserver() 103 .removeOnWindowAttachListener(mOnWindowAttachListener); 104 initializeJankTrackingComponents(); 105 } 106 }, REGISTRATION_DELAY_MS); 107 } 108 109 // Leave this empty. Only need to know when the DecorView is attached to the Window 110 // in order to get a handle to AttachedSurfaceControl. There is no need to tie 111 // anything to when the view is detached as all un-registration code is tied to 112 // the lifecycle of the enclosing activity. 113 @Override 114 public void onWindowDetached() { 115 116 } 117 }; 118 119 // TODO remove this once the viewroot_choreographer bugfix has been rolled out. b/399724640 JankTracker(Choreographer choreographer, View decorView)120 public JankTracker(Choreographer choreographer, View decorView) { 121 mStateTracker = new StateTracker(choreographer); 122 mJankDataProcessor = new JankDataProcessor(mStateTracker); 123 mDecorView = decorView; 124 mHandlerThread.start(); 125 registerWindowListeners(); 126 } 127 128 /** 129 * Using this constructor delays the instantiation of the StateTracker and JankDataProcessor 130 * until after the OnWindowAttachListener is fired and the instance of Choreographer attached to 131 * the ViewRootImpl can be passed to StateTracker. This should ensures the vsync ids we are 132 * using to keep track of active states line up with the ids that are being returned by 133 * OnJankDataListener. 134 */ JankTracker(View decorView)135 public JankTracker(View decorView) { 136 mDecorView = decorView; 137 mHandlerThread.start(); 138 registerWindowListeners(); 139 } 140 141 /** 142 * Merges app jank stats reported by components outside the platform to the current pending 143 * stats 144 */ mergeAppJankStats(AppJankStats appJankStats)145 public void mergeAppJankStats(AppJankStats appJankStats) { 146 if (appJankStats.getUid() != mAppUid) { 147 if (DEBUG) { 148 Log.d(DEBUG_KEY, "Reported JankStats AppUID does not match AppUID of " 149 + "enclosing activity."); 150 } 151 return; 152 } 153 getHandler().post(new Runnable() { 154 @Override 155 public void run() { 156 if (mJankDataProcessor == null) { 157 return; 158 } 159 mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); 160 } 161 }); 162 } 163 setActivityName(@onNull String activityName)164 public void setActivityName(@NonNull String activityName) { 165 mActivityName = activityName; 166 } 167 setAppUid(int uid)168 public void setAppUid(int uid) { 169 mAppUid = uid; 170 } 171 172 /** 173 * Will add the widget category, id and state as a UI state to associate frames to it. 174 * 175 * @param widgetCategory preselected general widget category 176 * @param widgetId developer defined widget id if available. 177 * @param widgetState the current active widget state. 178 */ addUiState(String widgetCategory, String widgetId, String widgetState)179 public void addUiState(String widgetCategory, String widgetId, String widgetState) { 180 if (!shouldTrack()) return; 181 182 mStateTracker.putState(widgetCategory, widgetId, widgetState); 183 } 184 185 /** 186 * Will remove the widget category, id and state as a ui state and no longer attribute frames 187 * to it. 188 * 189 * @param widgetCategory preselected general widget category 190 * @param widgetId developer defined widget id if available. 191 * @param widgetState no longer active widget state. 192 */ removeUiState(String widgetCategory, String widgetId, String widgetState)193 public void removeUiState(String widgetCategory, String widgetId, String widgetState) { 194 if (!shouldTrack()) return; 195 196 mStateTracker.removeState(widgetCategory, widgetId, widgetState); 197 } 198 199 /** 200 * Call to update a jank state to a different state. 201 * 202 * @param widgetCategory preselected general widget category. 203 * @param widgetId developer defined widget id if available. 204 * @param currentState current state of the widget. 205 * @param nextState the state the widget will be in. 206 */ updateUiState(String widgetCategory, String widgetId, String currentState, String nextState)207 public void updateUiState(String widgetCategory, String widgetId, String currentState, 208 String nextState) { 209 if (!shouldTrack()) return; 210 211 mStateTracker.updateState(widgetCategory, widgetId, currentState, nextState); 212 } 213 214 /** 215 * Will enable jank tracking, and add the activity as a state to associate frames to. 216 */ enableAppJankTracking()217 public void enableAppJankTracking() { 218 // Add the activity as a state, this will ensure we track frames to the activity without the 219 // need for a decorated widget to be used. 220 addActivityToStateTracking(); 221 mTrackingEnabled = true; 222 registerForJankData(); 223 } 224 225 /** 226 * Will disable jank tracking, and remove the activity as a state to associate frames to. 227 */ disableAppJankTracking()228 public void disableAppJankTracking() { 229 mTrackingEnabled = false; 230 removeActivityFromStateTracking(); 231 unregisterForJankData(); 232 } 233 234 /** 235 * Retrieve all pending widget states, this is intended for testing purposes only. If 236 * this is called before StateTracker has been created the method will just return without 237 * copying any data to the stateDataList parameter. 238 * 239 * @param stateDataList the ArrayList that will be populated with the pending states. 240 */ 241 @VisibleForTesting getAllUiStates(@onNull ArrayList<StateTracker.StateData> stateDataList)242 public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) { 243 if (mStateTracker == null) return; 244 mStateTracker.retrieveAllStates(stateDataList); 245 } 246 247 /** 248 * Retrieve all pending jank stats before they are logged, this is intended for testing 249 * purposes only. If this method is called before JankDataProcessor is created it will return 250 * an empty HashMap. 251 */ 252 @VisibleForTesting getPendingJankStats()253 public HashMap<String, JankDataProcessor.PendingJankStat> getPendingJankStats() { 254 if (mJankDataProcessor == null) { 255 return new HashMap<>(); 256 } 257 return mJankDataProcessor.getPendingJankStats(); 258 } 259 260 /** 261 * Only intended to be used by tests, the runnable that registers the listeners may not run 262 * in time for tests to pass. This forces them to run immediately. 263 */ 264 @VisibleForTesting forceListenerRegistration()265 public void forceListenerRegistration() { 266 addActivityToStateTracking(); 267 mSurfaceControl = mDecorView.getRootSurfaceControl(); 268 registerJankDataListener(); 269 mListenersRegistered = true; 270 } 271 unregisterForJankData()272 private void unregisterForJankData() { 273 if (mJankDataListenerRegistration == null) return; 274 275 if (com.android.window.flags.Flags.jankApi()) { 276 mJankDataListenerRegistration.release(); 277 } 278 mJankDataListenerRegistration = null; 279 mListenersRegistered = false; 280 } 281 registerForJankData()282 private void registerForJankData() { 283 if (mDecorView == null) return; 284 285 mSurfaceControl = mDecorView.getRootSurfaceControl(); 286 287 if (mSurfaceControl == null || mListenersRegistered) return; 288 289 // Wait a short time before registering the listener. During development it was observed 290 // that if a listener is registered too quickly after a hot or warm start no data is 291 // received b/394956095. 292 getHandler().postDelayed(new Runnable() { 293 @Override 294 public void run() { 295 registerJankDataListener(); 296 } 297 }, REGISTRATION_DELAY_MS); 298 } 299 300 /** 301 * Returns whether jank tracking is enabled or not. 302 */ 303 @VisibleForTesting shouldTrack()304 public boolean shouldTrack() { 305 if (DEBUG) { 306 Log.d(DEBUG_KEY, String.format("mTrackingEnabled: %s | mListenersRegistered: %s", 307 mTrackingEnabled, mListenersRegistered)); 308 } 309 return mTrackingEnabled && mListenersRegistered; 310 } 311 312 /** 313 * Need to know when the decor view gets attached to the window in order to get 314 * AttachedSurfaceControl. In order to register a callback for OnJankDataListener 315 * AttachedSurfaceControl needs to be created which only happens after onWindowAttached is 316 * called. This is why there is a delay in posting the runnable. 317 */ registerWindowListeners()318 private void registerWindowListeners() { 319 if (mDecorView == null) return; 320 mDecorView.getViewTreeObserver().addOnWindowAttachListener(mOnWindowAttachListener); 321 } 322 registerJankDataListener()323 private void registerJankDataListener() { 324 if (mSurfaceControl == null) { 325 if (DEBUG) { 326 Log.d(DEBUG_KEY, "SurfaceControl is Null"); 327 } 328 return; 329 } 330 331 if (com.android.window.flags.Flags.jankApi()) { 332 mJankDataListenerRegistration = mSurfaceControl.registerOnJankDataListener( 333 mHandlerThread.getThreadExecutor(), mJankDataListener); 334 335 if (mJankDataListenerRegistration 336 == SurfaceControl.OnJankDataListenerRegistration.NONE) { 337 if (DEBUG) { 338 Log.d(DEBUG_KEY, "OnJankDataListenerRegistration is assigned NONE"); 339 } 340 return; 341 } 342 mListenersRegistered = true; 343 } 344 } 345 getHandler()346 private Handler getHandler() { 347 if (mHandler == null) { 348 mHandler = new Handler(mHandlerThread.getLooper()); 349 } 350 return mHandler; 351 } 352 addActivityToStateTracking()353 private void addActivityToStateTracking() { 354 if (mStateTracker == null) return; 355 356 mStateTracker.putState(AppJankStats.WIDGET_CATEGORY_UNSPECIFIED, mActivityName, 357 AppJankStats.WIDGET_STATE_UNSPECIFIED); 358 } 359 removeActivityFromStateTracking()360 private void removeActivityFromStateTracking() { 361 if (mStateTracker == null) return; 362 363 mStateTracker.removeState(AppJankStats.WIDGET_CATEGORY_UNSPECIFIED, mActivityName, 364 AppJankStats.WIDGET_STATE_UNSPECIFIED); 365 } 366 initializeJankTrackingComponents()367 private void initializeJankTrackingComponents() { 368 ViewRootImpl viewRoot = mDecorView.getViewRootImpl(); 369 if (viewRoot == null || viewRoot.getChoreographer() == null) { 370 return; 371 } 372 373 if (mStateTracker == null) { 374 mStateTracker = new StateTracker(viewRoot.getChoreographer()); 375 } 376 377 if (mJankDataProcessor == null) { 378 mJankDataProcessor = new JankDataProcessor(mStateTracker); 379 } 380 381 addActivityToStateTracking(); 382 registerForJankData(); 383 } 384 } 385