Loading core/java/android/app/jank/JankTracker.java +103 −33 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.AttachedSurfaceControl; import android.view.Choreographer; import android.view.SurfaceControl; Loading @@ -30,16 +31,22 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * This class is responsible for registering callbacks that will receive JankData batches. * It handles managing the background thread that JankData will be processed on. As well as acting * as an intermediary between widgets and the state tracker, routing state changes to the tracker. * * @hide */ @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) public class JankTracker { private static final boolean DEBUG = false; private static final String DEBUG_KEY = "JANKTRACKER"; // How long to delay the JankData listener registration. //TODO b/394956095 see if this can be reduced or eliminated. private static final int REGISTRATION_DELAY_MS = 1000; // Tracks states reported by widgets. private StateTracker mStateTracker; // Processes JankData batches and associates frames to widget states. Loading @@ -49,9 +56,6 @@ public class JankTracker { private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker"); private Handler mHandler = null; // Needed so we know when the view is attached to a window. private ViewTreeObserver mViewTreeObserver; // Handle to a registered OnJankData listener. private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration; Loading @@ -76,6 +80,40 @@ public class JankTracker { */ private boolean mListenersRegistered = false; @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API) private final SurfaceControl.OnJankDataListener mJankDataListener = new SurfaceControl.OnJankDataListener() { @Override public void onJankDataAvailable( @androidx.annotation.NonNull List<SurfaceControl.JankData> jankData) { if (mJankDataProcessor == null) return; mJankDataProcessor.processJankData(jankData, mActivityName, mAppUid); } }; private final ViewTreeObserver.OnWindowAttachListener mOnWindowAttachListener = new ViewTreeObserver.OnWindowAttachListener() { @Override public void onWindowAttached() { getHandler().postDelayed(new Runnable() { @Override public void run() { mDecorView.getViewTreeObserver() .removeOnWindowAttachListener(mOnWindowAttachListener); registerForJankData(); } }, REGISTRATION_DELAY_MS); } // Leave this empty. Only need to know when the DecorView is attached to the Window // in order to get a handle to AttachedSurfaceControl. There is no need to tie // anything to when the view is detached as all un-registration code is tied to // the lifecycle of the enclosing activity. @Override public void onWindowDetached() { } }; public JankTracker(Choreographer choreographer, View decorView) { mStateTracker = new StateTracker(choreographer); Loading Loading @@ -108,6 +146,7 @@ public class JankTracker { /** * Will add the widget category, id and state as a UI state to associate frames to it. * * @param widgetCategory preselected general widget category * @param widgetId developer defined widget id if available. * @param widgetState the current active widget state. Loading @@ -121,6 +160,7 @@ public class JankTracker { /** * Will remove the widget category, id and state as a ui state and no longer attribute frames * to it. * * @param widgetCategory preselected general widget category * @param widgetId developer defined widget id if available. * @param widgetState no longer active widget state. Loading @@ -133,6 +173,7 @@ public class JankTracker { /** * Call to update a jank state to a different state. * * @param widgetCategory preselected general widget category. * @param widgetId developer defined widget id if available. * @param currentState current state of the widget. Loading @@ -150,10 +191,11 @@ public class JankTracker { */ public void enableAppJankTracking() { // Add the activity as a state, this will ensure we track frames to the activity without the // need of a decorated widget to be used. // need for a decorated widget to be used. // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged. mStateTracker.putState("NONE", mActivityName, "NONE"); mTrackingEnabled = true; registerForJankData(); } /** Loading @@ -163,10 +205,12 @@ public class JankTracker { mTrackingEnabled = false; // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged. mStateTracker.removeState("NONE", mActivityName, "NONE"); unregisterForJankData(); } /** * Retrieve all pending widget states, this is intended for testing purposes only. * * @param stateDataList the ArrayList that will be populated with the pending states. */ @VisibleForTesting Loading @@ -190,16 +234,35 @@ public class JankTracker { @VisibleForTesting public void forceListenerRegistration() { mSurfaceControl = mDecorView.getRootSurfaceControl(); registerForJankData(); // TODO b/376116199 Check if registration is good. mListenersRegistered = true; registerJankDataListener(); } private void unregisterForJankData() { if (mJankDataListenerRegistration == null) return; if (com.android.window.flags.Flags.jankApi()) { mJankDataListenerRegistration.release(); } mJankDataListenerRegistration = null; mListenersRegistered = false; } private void registerForJankData() { if (mSurfaceControl == null) return; /* TODO b/376115668 Register for JankData batches from new JankTracking API */ if (mDecorView == null) return; mSurfaceControl = mDecorView.getRootSurfaceControl(); if (mSurfaceControl == null || mListenersRegistered) return; // Wait a short time before registering the listener. During development it was observed // that if a listener is registered too quickly after a hot or warm start no data is // received b/394956095. getHandler().postDelayed(new Runnable() { @Override public void run() { registerJankDataListener(); } }, REGISTRATION_DELAY_MS); } /** Loading @@ -218,23 +281,30 @@ public class JankTracker { */ private void registerWindowListeners() { if (mDecorView == null) return; mViewTreeObserver = mDecorView.getViewTreeObserver(); mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() { @Override public void onWindowAttached() { getHandler().postDelayed(new Runnable() { @Override public void run() { forceListenerRegistration(); mDecorView.getViewTreeObserver().addOnWindowAttachListener(mOnWindowAttachListener); } }, 1000); private void registerJankDataListener() { if (mSurfaceControl == null) { if (DEBUG) { Log.d(DEBUG_KEY, "SurfaceControl is Null"); } return; } @Override public void onWindowDetached() { // TODO b/376116199 do we un-register the callback or just not process the data. if (com.android.window.flags.Flags.jankApi()) { mJankDataListenerRegistration = mSurfaceControl.registerOnJankDataListener( mHandlerThread.getThreadExecutor(), mJankDataListener); if (mJankDataListenerRegistration == SurfaceControl.OnJankDataListenerRegistration.NONE) { if (DEBUG) { Log.d(DEBUG_KEY, "OnJankDataListenerRegistration is assigned NONE"); } return; } mListenersRegistered = true; } }); } private Handler getHandler() { Loading tests/AppJankTest/res/values/strings.xml 0 → 100644 +3 −0 Original line number Diff line number Diff line <resources> <string name="continue_test">Continue Test</string> </resources> No newline at end of file tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java +2 −1 Original line number Diff line number Diff line Loading @@ -209,7 +209,8 @@ public class IntegrationTests { JankTrackerActivity.class); resumeJankTracker.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); mEmptyActivity.startActivity(resumeJankTracker); mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS); mDevice.wait(Until.findObject(By.text(mEmptyActivity.getString(R.string.continue_test))), WAIT_FOR_TIMEOUT_MS); assertTrue(jankTracker.shouldTrack()); } Loading tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java +26 −0 Original line number Diff line number Diff line Loading @@ -18,15 +18,41 @@ package android.app.jank.tests; import android.app.Activity; import android.os.Bundle; import android.widget.EditText; public class JankTrackerActivity extends Activity { private static final int CONTINUE_TEST_DELAY_MS = 4000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.jank_tracker_activity_layout); } /** * In IntegrationTests#jankTrackingResumed_whenActivityBecomesVisibleAgain this activity is * placed into the background and then resumed via an intent. The test waits until the * `continue_test` string is visible on the screen before validating that Jank tracking has * resumed. * * <p>The 4 second delay allows JankTracker to re-register its callbacks and start receiving * JankData before the test proceeds. */ @Override protected void onResume() { super.onResume(); getActivityThread().getHandler().postDelayed(new Runnable() { @Override public void run() { EditText editTextView = findViewById(R.id.edit_text); if (editTextView != null) { editTextView.setText(R.string.continue_test); } } }, CONTINUE_TEST_DELAY_MS); } } Loading
core/java/android/app/jank/JankTracker.java +103 −33 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.AttachedSurfaceControl; import android.view.Choreographer; import android.view.SurfaceControl; Loading @@ -30,16 +31,22 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * This class is responsible for registering callbacks that will receive JankData batches. * It handles managing the background thread that JankData will be processed on. As well as acting * as an intermediary between widgets and the state tracker, routing state changes to the tracker. * * @hide */ @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) public class JankTracker { private static final boolean DEBUG = false; private static final String DEBUG_KEY = "JANKTRACKER"; // How long to delay the JankData listener registration. //TODO b/394956095 see if this can be reduced or eliminated. private static final int REGISTRATION_DELAY_MS = 1000; // Tracks states reported by widgets. private StateTracker mStateTracker; // Processes JankData batches and associates frames to widget states. Loading @@ -49,9 +56,6 @@ public class JankTracker { private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker"); private Handler mHandler = null; // Needed so we know when the view is attached to a window. private ViewTreeObserver mViewTreeObserver; // Handle to a registered OnJankData listener. private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration; Loading @@ -76,6 +80,40 @@ public class JankTracker { */ private boolean mListenersRegistered = false; @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API) private final SurfaceControl.OnJankDataListener mJankDataListener = new SurfaceControl.OnJankDataListener() { @Override public void onJankDataAvailable( @androidx.annotation.NonNull List<SurfaceControl.JankData> jankData) { if (mJankDataProcessor == null) return; mJankDataProcessor.processJankData(jankData, mActivityName, mAppUid); } }; private final ViewTreeObserver.OnWindowAttachListener mOnWindowAttachListener = new ViewTreeObserver.OnWindowAttachListener() { @Override public void onWindowAttached() { getHandler().postDelayed(new Runnable() { @Override public void run() { mDecorView.getViewTreeObserver() .removeOnWindowAttachListener(mOnWindowAttachListener); registerForJankData(); } }, REGISTRATION_DELAY_MS); } // Leave this empty. Only need to know when the DecorView is attached to the Window // in order to get a handle to AttachedSurfaceControl. There is no need to tie // anything to when the view is detached as all un-registration code is tied to // the lifecycle of the enclosing activity. @Override public void onWindowDetached() { } }; public JankTracker(Choreographer choreographer, View decorView) { mStateTracker = new StateTracker(choreographer); Loading Loading @@ -108,6 +146,7 @@ public class JankTracker { /** * Will add the widget category, id and state as a UI state to associate frames to it. * * @param widgetCategory preselected general widget category * @param widgetId developer defined widget id if available. * @param widgetState the current active widget state. Loading @@ -121,6 +160,7 @@ public class JankTracker { /** * Will remove the widget category, id and state as a ui state and no longer attribute frames * to it. * * @param widgetCategory preselected general widget category * @param widgetId developer defined widget id if available. * @param widgetState no longer active widget state. Loading @@ -133,6 +173,7 @@ public class JankTracker { /** * Call to update a jank state to a different state. * * @param widgetCategory preselected general widget category. * @param widgetId developer defined widget id if available. * @param currentState current state of the widget. Loading @@ -150,10 +191,11 @@ public class JankTracker { */ public void enableAppJankTracking() { // Add the activity as a state, this will ensure we track frames to the activity without the // need of a decorated widget to be used. // need for a decorated widget to be used. // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged. mStateTracker.putState("NONE", mActivityName, "NONE"); mTrackingEnabled = true; registerForJankData(); } /** Loading @@ -163,10 +205,12 @@ public class JankTracker { mTrackingEnabled = false; // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged. mStateTracker.removeState("NONE", mActivityName, "NONE"); unregisterForJankData(); } /** * Retrieve all pending widget states, this is intended for testing purposes only. * * @param stateDataList the ArrayList that will be populated with the pending states. */ @VisibleForTesting Loading @@ -190,16 +234,35 @@ public class JankTracker { @VisibleForTesting public void forceListenerRegistration() { mSurfaceControl = mDecorView.getRootSurfaceControl(); registerForJankData(); // TODO b/376116199 Check if registration is good. mListenersRegistered = true; registerJankDataListener(); } private void unregisterForJankData() { if (mJankDataListenerRegistration == null) return; if (com.android.window.flags.Flags.jankApi()) { mJankDataListenerRegistration.release(); } mJankDataListenerRegistration = null; mListenersRegistered = false; } private void registerForJankData() { if (mSurfaceControl == null) return; /* TODO b/376115668 Register for JankData batches from new JankTracking API */ if (mDecorView == null) return; mSurfaceControl = mDecorView.getRootSurfaceControl(); if (mSurfaceControl == null || mListenersRegistered) return; // Wait a short time before registering the listener. During development it was observed // that if a listener is registered too quickly after a hot or warm start no data is // received b/394956095. getHandler().postDelayed(new Runnable() { @Override public void run() { registerJankDataListener(); } }, REGISTRATION_DELAY_MS); } /** Loading @@ -218,23 +281,30 @@ public class JankTracker { */ private void registerWindowListeners() { if (mDecorView == null) return; mViewTreeObserver = mDecorView.getViewTreeObserver(); mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() { @Override public void onWindowAttached() { getHandler().postDelayed(new Runnable() { @Override public void run() { forceListenerRegistration(); mDecorView.getViewTreeObserver().addOnWindowAttachListener(mOnWindowAttachListener); } }, 1000); private void registerJankDataListener() { if (mSurfaceControl == null) { if (DEBUG) { Log.d(DEBUG_KEY, "SurfaceControl is Null"); } return; } @Override public void onWindowDetached() { // TODO b/376116199 do we un-register the callback or just not process the data. if (com.android.window.flags.Flags.jankApi()) { mJankDataListenerRegistration = mSurfaceControl.registerOnJankDataListener( mHandlerThread.getThreadExecutor(), mJankDataListener); if (mJankDataListenerRegistration == SurfaceControl.OnJankDataListenerRegistration.NONE) { if (DEBUG) { Log.d(DEBUG_KEY, "OnJankDataListenerRegistration is assigned NONE"); } return; } mListenersRegistered = true; } }); } private Handler getHandler() { Loading
tests/AppJankTest/res/values/strings.xml 0 → 100644 +3 −0 Original line number Diff line number Diff line <resources> <string name="continue_test">Continue Test</string> </resources> No newline at end of file
tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java +2 −1 Original line number Diff line number Diff line Loading @@ -209,7 +209,8 @@ public class IntegrationTests { JankTrackerActivity.class); resumeJankTracker.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); mEmptyActivity.startActivity(resumeJankTracker); mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS); mDevice.wait(Until.findObject(By.text(mEmptyActivity.getString(R.string.continue_test))), WAIT_FOR_TIMEOUT_MS); assertTrue(jankTracker.shouldTrack()); } Loading
tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java +26 −0 Original line number Diff line number Diff line Loading @@ -18,15 +18,41 @@ package android.app.jank.tests; import android.app.Activity; import android.os.Bundle; import android.widget.EditText; public class JankTrackerActivity extends Activity { private static final int CONTINUE_TEST_DELAY_MS = 4000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.jank_tracker_activity_layout); } /** * In IntegrationTests#jankTrackingResumed_whenActivityBecomesVisibleAgain this activity is * placed into the background and then resumed via an intent. The test waits until the * `continue_test` string is visible on the screen before validating that Jank tracking has * resumed. * * <p>The 4 second delay allows JankTracker to re-register its callbacks and start receiving * JankData before the test proceeds. */ @Override protected void onResume() { super.onResume(); getActivityThread().getHandler().postDelayed(new Runnable() { @Override public void run() { EditText editTextView = findViewById(R.id.edit_text); if (editTextView != null) { editTextView.setText(R.string.continue_test); } } }, CONTINUE_TEST_DELAY_MS); } }