diff options
author | 2025-02-14 13:05:52 -0800 | |
---|---|---|
committer | 2025-02-14 13:05:52 -0800 | |
commit | f93f2b6b027a32c0736cead7907197a33da12c2a (patch) | |
tree | 153873085fa9ecb9f2871460dcfdfdd62eaa6963 | |
parent | ef4af100c811e0463f56521f6ce687f24ee91bbb (diff) | |
parent | 560388cb1eff73efd8b352abeefd797c4538ca03 (diff) |
Merge "Register For JankData" into main
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); + } } |