diff options
| author | 2025-02-05 21:14:30 +0000 | |
|---|---|---|
| committer | 2025-02-14 04:24:53 +0000 | |
| commit | 560388cb1eff73efd8b352abeefd797c4538ca03 (patch) | |
| tree | baab94ce377e3bc592edbf22aed2c44164cf401b | |
| parent | 58b9f87c1174addde404f7ba2f0599f4a7ac7f44 (diff) | |
Register For JankData
This change registers a listener that will receive batches of JankData
from Surfaceflinger. The registratoin and unregistration of the callback
is tied to the lifecycle of the enclosing activity. When an activity is
created or resumed a new listener will be registered. When an activity
is pasued or deleted the callback will be unregistered.
When a batch of JankData is received it is passed to the
JankDataProcessor, which will perform frame metric aggregation.
Bug: 376115668
Flag: android.app.jank.detailed_app_jank_metrics_logging_enabled
Test: atest CoreAppJankTestCases
Change-Id: I76ea652bdf54abe3e03d76c5125d69b029babd34
4 files changed, 134 insertions, 34 deletions
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java index a04f96a9f6e3..9c85b09f6be3 100644 --- a/core/java/android/app/jank/JankTracker.java +++ b/core/java/android/app/jank/JankTracker.java @@ -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; @@ -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. @@ -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; @@ -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); @@ -108,9 +146,10 @@ 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. + * @param widgetId developer defined widget id if available. + * @param widgetState the current active widget state. */ public void addUiState(String widgetCategory, String widgetId, String widgetState) { if (!shouldTrack()) return; @@ -121,9 +160,10 @@ 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. + * @param widgetId developer defined widget id if available. + * @param widgetState no longer active widget state. */ public void removeUiState(String widgetCategory, String widgetId, String widgetState) { if (!shouldTrack()) return; @@ -133,10 +173,11 @@ 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. - * @param nextState the state the widget will be in. + * @param widgetId developer defined widget id if available. + * @param currentState current state of the widget. + * @param nextState the state the widget will be in. */ public void updateUiState(String widgetCategory, String widgetId, String currentState, String nextState) { @@ -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(); } /** @@ -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 @@ -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); } /** @@ -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(); - } - }, 1000); + mDecorView.getViewTreeObserver().addOnWindowAttachListener(mOnWindowAttachListener); + } + + 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() { diff --git a/tests/AppJankTest/res/values/strings.xml b/tests/AppJankTest/res/values/strings.xml new file mode 100644 index 000000000000..ab2d18fa9d53 --- /dev/null +++ b/tests/AppJankTest/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="continue_test">Continue Test</string> +</resources>
\ No newline at end of file diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java index fe9f63615757..3498974b348e 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java +++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java @@ -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()); } diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java index 80ab6ad3e587..686758200853 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java @@ -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); + } } |