• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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