From fd6f435e3091497a773e77d5d90be5655656a9ed Mon Sep 17 00:00:00 2001 From: Steven Terrell Date: Sun, 29 Sep 2024 20:52:20 +0000 Subject: API Changes and Update Activity This change adds the ability for apps to report jank stats back to the platform. It adds the reporting api surface to the view class and instantiates the JankTracker object inside an Activity. It also adds a callback to the DecorView that allows it to obtain a reference to the JankTracker object contained in the Activity. Bug: 375487792 Test: atest CoreAppJankTestCases Flag: android.app.jank.detailed_app_jank_metrics_api API-Coverage-Bug: 377714256 Change-Id: If5de4008beba82ecaf9601baea93e69b3cba9299 --- core/api/current.txt | 41 ++++ core/java/android/app/Activity.java | 58 +++++ core/java/android/app/jank/AppJankStats.java | 253 +++++++++++++++++++++ .../android/app/jank/FrameOverrunHistogram.java | 107 +++++++++ core/java/android/app/jank/JankDataProcessor.java | 8 + core/java/android/app/jank/JankTracker.java | 8 + core/java/android/view/View.java | 19 ++ .../com/android/internal/policy/DecorView.java | 39 ++++ tests/AppJankTest/AndroidManifest.xml | 2 +- .../android/app/jank/tests/JankTrackerTest.java | 15 ++ 10 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 core/java/android/app/jank/AppJankStats.java create mode 100644 core/java/android/app/jank/FrameOverrunHistogram.java diff --git a/core/api/current.txt b/core/api/current.txt index aa58fe42fa26..653f907635ba 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9094,6 +9094,46 @@ package android.app.blob { } +package android.app.jank { + + @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats { + ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.FrameOverrunHistogram); + method @NonNull public android.app.jank.FrameOverrunHistogram getFrameOverrunHistogram(); + method public long getJankyFrameCount(); + method public long getTotalFrameCount(); + method public int getUid(); + method @NonNull public String getWidgetCategory(); + method @NonNull public String getWidgetId(); + method @NonNull public String getWidgetState(); + field public static final String ANIMATING = "animating"; + field public static final String ANIMATION = "animation"; + field public static final String DRAGGING = "dragging"; + field public static final String FLINGING = "flinging"; + field public static final String KEYBOARD = "keyboard"; + field public static final String MEDIA = "media"; + field public static final String NAVIGATION = "navigation"; + field public static final String NONE = "none"; + field public static final String OTHER = "other"; + field public static final String PLAYBACK = "playback"; + field public static final String PREDICTIVE_BACK = "predictive_back"; + field public static final String SCROLL = "scroll"; + field public static final String SCROLLING = "scrolling"; + field public static final String SWIPING = "swiping"; + field public static final String TAPPING = "tapping"; + field public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified"; + field public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; + field public static final String ZOOMING = "zooming"; + } + + @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class FrameOverrunHistogram { + ctor public FrameOverrunHistogram(); + method public void addFrameOverrunMillis(int); + method @NonNull public int[] getBucketCounters(); + method @NonNull public int[] getBucketEndpointsMillis(); + } + +} + package android.app.job { public class JobInfo implements android.os.Parcelable { @@ -53459,6 +53499,7 @@ package android.view { method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void removeOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener); + method @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public void reportAppJankStats(@NonNull android.app.jank.AppJankStats); method public void requestApplyInsets(); method @Deprecated public void requestFitSystemWindows(); method public final boolean requestFocus(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7a1c759a3ec4..198d9ea4d129 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -53,6 +53,7 @@ import android.app.VoiceInteractor.Request; import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; import android.app.compat.CompatChanges; +import android.app.jank.JankTracker; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -123,6 +124,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SuperNotCalledException; import android.view.ActionMode; +import android.view.Choreographer; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; @@ -174,6 +176,7 @@ import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; import com.android.internal.util.dump.DumpableContainerImpl; @@ -1144,6 +1147,9 @@ public class Activity extends ContextThemeWrapper }; + @Nullable + private JankTracker mJankTracker; + private static native String getDlWarning(); /** @@ -2244,6 +2250,10 @@ public class Activity extends ContextThemeWrapper // Notify autofill getAutofillClientController().onActivityPostResumed(); + if (android.app.jank.Flags.detailedAppJankMetricsApi()) { + startAppJankTracking(); + } + mCalled = true; } @@ -9246,6 +9256,9 @@ public class Activity extends ContextThemeWrapper dispatchActivityPrePaused(); mDoReportFullyDrawn = false; mFragments.dispatchPause(); + if (android.app.jank.Flags.detailedAppJankMetricsApi()) { + stopAppJankTracking(); + } mCalled = false; final long startTime = SystemClock.uptimeMillis(); onPause(); @@ -9924,4 +9937,49 @@ public class Activity extends ContextThemeWrapper mScreenCaptureCallbackHandler.unregisterScreenCaptureCallback(callback); } } + + /** + * Enabling jank tracking for this activity but only if certain conditions are met. The + * application must have an app category other than undefined and a visible view. + */ + private void startAppJankTracking() { + if (!android.app.jank.Flags.detailedAppJankMetricsLoggingEnabled()) { + return; + } + if (mApplication.getApplicationInfo().category == ApplicationInfo.CATEGORY_UNDEFINED) { + return; + } + if (getWindow() != null && getWindow().peekDecorView() != null) { + DecorView decorView = (DecorView) getWindow().peekDecorView(); + if (decorView.getVisibility() == View.VISIBLE) { + decorView.setAppJankStatsCallback(new DecorView.AppJankStatsCallback() { + @Override + public JankTracker getAppJankTracker() { + return mJankTracker; + } + }); + if (mJankTracker == null) { + // TODO b/377960907 use the Choreographer attached to the ViewRootImpl instead. + mJankTracker = new JankTracker(Choreographer.getInstance(), + decorView); + } + // TODO b/377674765 confirm this is the string we want logged. + mJankTracker.setActivityName(getComponentName().getClassName()); + mJankTracker.setAppUid(myUid()); + mJankTracker.enableAppJankTracking(); + } + } + } + + /** + * Call to disable jank tracking for this activity. + */ + private void stopAppJankTracking() { + if (!android.app.jank.Flags.detailedAppJankMetricsLoggingEnabled()) { + return; + } + if (mJankTracker != null) { + mJankTracker.disableAppJankTracking(); + } + } } diff --git a/core/java/android/app/jank/AppJankStats.java b/core/java/android/app/jank/AppJankStats.java new file mode 100644 index 000000000000..eea1d2ba5b9e --- /dev/null +++ b/core/java/android/app/jank/AppJankStats.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.jank; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This class stores detailed jank statistics for an individual UI widget. These statistics + * provide performance insights for specific UI widget states by correlating the number of + * "Janky frames" with the total frames rendered while the widget is in that state. This class + * can be used by library widgets to provide the system with more detailed information about + * where jank is happening for diagnostic purposes. + */ +@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) +public final class AppJankStats { + // UID of the app + private int mUid; + + // The id that has been set for the widget. + private String mWidgetId; + + // A general category that the widget applies to. + private String mWidgetCategory; + + // The states that the UI elements can report + private String mWidgetState; + + // The number of frames reported during this state. + private long mTotalFrames; + + // Total number of frames determined to be janky during the reported state. + private long mJankyFrames; + + // Histogram of frame duration overruns encoded in predetermined buckets. + private FrameOverrunHistogram mFrameOverrunHistogram; + + + /** Used to indicate no widget category has been set. */ + public static final String WIDGET_CATEGORY_UNSPECIFIED = + "widget_category_unspecified"; + + /** UI elements that facilitate scrolling. */ + public static final String SCROLL = "scroll"; + + /** UI elements that facilitate playing animations. */ + public static final String ANIMATION = "animation"; + + /** UI elements that facilitate media playback. */ + public static final String MEDIA = "media"; + + /** UI elements that facilitate in-app navigation. */ + public static final String NAVIGATION = "navigation"; + + /** UI elements that facilitate displaying, hiding or interacting with keyboard. */ + public static final String KEYBOARD = "keyboard"; + + /** UI elements that facilitate predictive back gesture navigation. */ + public static final String PREDICTIVE_BACK = "predictive_back"; + + /** UI elements that don't fall in one or any of the other categories. */ + public static final String OTHER = "other"; + + /** Used to indicate no widget state has been set. */ + public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; + + /** Used to indicate the UI element currently has no state and is idle. */ + public static final String NONE = "none"; + + /** Used to indicate the UI element is currently scrolling. */ + public static final String SCROLLING = "scrolling"; + + /** Used to indicate the UI element is currently being flung. */ + public static final String FLINGING = "flinging"; + + /** Used to indicate the UI element is currently being swiped. */ + public static final String SWIPING = "swiping"; + + /** Used to indicate the UI element is currently being dragged. */ + public static final String DRAGGING = "dragging"; + + /** Used to indicate the UI element is currently zooming. */ + public static final String ZOOMING = "zooming"; + + /** Used to indicate the UI element is currently animating. */ + public static final String ANIMATING = "animating"; + + /** Used to indicate the UI element is currently playing media. */ + public static final String PLAYBACK = "playback"; + + /** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */ + public static final String TAPPING = "tapping"; + + + /** + * @hide + */ + @StringDef(value = { + WIDGET_CATEGORY_UNSPECIFIED, + SCROLL, + ANIMATION, + MEDIA, + NAVIGATION, + KEYBOARD, + PREDICTIVE_BACK, + OTHER + }) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @Retention(RetentionPolicy.SOURCE) + public @interface WidgetCategory { + } + /** + * @hide + */ + @StringDef(value = { + WIDGET_STATE_UNSPECIFIED, + NONE, + SCROLLING, + FLINGING, + SWIPING, + DRAGGING, + ZOOMING, + ANIMATING, + PLAYBACK, + TAPPING, + }) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @Retention(RetentionPolicy.SOURCE) + public @interface WidgetState { + } + + + /** + * Creates a new AppJankStats object. + * + * @param appUid the Uid of the App that is collecting jank stats. + * @param widgetId the widget id that frames will be associated to. + * @param widgetCategory a general functionality category that the widget falls into. Must be + * one of the following: SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, + * PREDICTIVE_BACK, OTHER or will be set to WIDGET_CATEGORY_UNSPECIFIED + * if no value is passed. + * @param widgetState the state the widget was in while frames were counted. Must be one of + * the following: NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, + * ANIMATING, PLAYBACK, TAPPING or will be set to WIDGET_STATE_UNSPECIFIED + * if no value is passed. + * @param totalFrames the total number of frames that were counted for this stat. + * @param jankyFrames the total number of janky frames that were counted for this stat. + * @param frameOverrunHistogram the histogram with predefined buckets. See + * {@link #getFrameOverrunHistogram()} for details. + * + */ + public AppJankStats(int appUid, @NonNull String widgetId, + @Nullable @WidgetCategory String widgetCategory, + @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames, + @NonNull FrameOverrunHistogram frameOverrunHistogram) { + mUid = appUid; + mWidgetId = widgetId; + mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED; + mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED; + mTotalFrames = totalFrames; + mJankyFrames = jankyFrames; + mFrameOverrunHistogram = frameOverrunHistogram; + } + + /** + * Returns the app uid. + * + * @return the app uid. + */ + public int getUid() { + return mUid; + } + + /** + * Returns the id of the widget that reported state changes. + * + * @return the id of the widget that reported state changes. This value cannot be null. + */ + public @NonNull String getWidgetId() { + return mWidgetId; + } + + /** + * Returns the category that the widget's functionality generally falls into, or + * widget_category_unspecified {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in. + * + * @return the category that the widget's functionality generally falls into, this value cannot + * be null. + */ + public @NonNull @WidgetCategory String getWidgetCategory() { + return mWidgetCategory; + } + + /** + * Returns the widget's state that was reported for this stat, or widget_state_unspecified + * {@link #WIDGET_STATE_UNSPECIFIED} if no value was passed in. + * + * @return the widget's state that was reported for this stat. This value cannot be null. + */ + public @NonNull @WidgetState String getWidgetState() { + return mWidgetState; + } + + /** + * Returns the number of frames that were determined to be janky for this stat. + * + * @return the number of frames that were determined to be janky for this stat. + */ + public long getJankyFrameCount() { + return mJankyFrames; + } + + /** + * Returns the total number of frames counted for this stat. + * + * @return the total number of frames counted for this stat. + */ + public long getTotalFrameCount() { + return mTotalFrames; + } + + /** + * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets. + * See {@link FrameOverrunHistogram} for more information. + * + * @return Histogram containing frame overrun times in predefined buckets. This value cannot + * be null. + */ + public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() { + return mFrameOverrunHistogram; + } +} diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java new file mode 100644 index 000000000000..e28ac126a90a --- /dev/null +++ b/core/java/android/app/jank/FrameOverrunHistogram.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.jank; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; + +import java.util.Arrays; + +/** + * This class is intended to be used when reporting {@link AppJankStats} back to the system. It's + * intended to be used by library widgets to help facilitate the reporting of frame overrun times + * by adding those times into predefined buckets. + */ +@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) +public class FrameOverrunHistogram { + private static int[] sBucketEndpoints = new int[]{ + Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18, + -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40, + 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000 + }; + private int[] mBucketCounts; + + /** + * Create a new instance of FrameOverrunHistogram. + */ + public FrameOverrunHistogram() { + mBucketCounts = new int[sBucketEndpoints.length - 1]; + } + + /** + * Increases the count by one for the bucket representing the frame overrun duration. + * + * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference + * between a frames deadline and when it was rendered. + */ + public void addFrameOverrunMillis(int frameOverrunMillis) { + int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis); + mBucketCounts[countsIndex]++; + } + + /** + * Returns the counts for the all the frame overrun buckets. + * + * @return an array of integers representing the counts of frame overrun times. This value + * cannot be null. + */ + public @NonNull int[] getBucketCounters() { + return Arrays.copyOf(mBucketCounts, mBucketCounts.length); + } + + /** + * Returns the predefined endpoints for the histogram. + * + * @return array of integers representing the endpoints for the predefined histogram count + * buckets. This value cannot be null. + */ + public @NonNull int[] getBucketEndpointsMillis() { + return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length); + } + + // This takes the overrun time and returns what bucket it belongs to in the counters array. + private int getIndexForCountsFromOverrunTime(int overrunTime) { + if (overrunTime < 20) { + if (overrunTime >= -20) { + return (overrunTime + 20) / 2 + 12; + } + if (overrunTime >= -30) { + return (overrunTime + 30) / 5 + 10; + } + if (overrunTime >= -100) { + return (overrunTime + 100) / 10 + 3; + } + if (overrunTime >= -200) { + return (overrunTime + 200) / 50 + 1; + } + return 0; + } + if (overrunTime < 30) { + return (overrunTime - 20) / 5 + 32; + } + if (overrunTime < 100) { + return (overrunTime - 30) / 10 + 34; + } + if (overrunTime < 200) { + return (overrunTime - 50) / 100 + 41; + } + if (overrunTime < 1000) { + return (overrunTime - 200) / 100 + 43; + } + return sBucketEndpoints.length - 1; + } +} diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 981a9167c2da..3783a5f9e829 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -86,6 +86,14 @@ public class JankDataProcessor { jankDataProcessingComplete(); } + /** + * Merges app jank stats reported by components outside the platform to the current pending + * stats + */ + public void mergeJankStats(AppJankStats jankStats, String activityName) { + // TODO b/377572463 Add Merging Logic + } + /** * Returns the aggregate map of different pending jank stats. */ diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java index df422e0069c5..202281f98c97 100644 --- a/core/java/android/app/jank/JankTracker.java +++ b/core/java/android/app/jank/JankTracker.java @@ -84,6 +84,14 @@ public class JankTracker { registerWindowListeners(); } + /** + * Merges app jank stats reported by components outside the platform to the current pending + * stats + */ + public void mergeAppJankStats(AppJankStats appJankStats) { + mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); + } + public void setActivityName(@NonNull String activityName) { mActivityName = activityName; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5ee229f87a1e..beb9ca886ae0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -90,6 +90,8 @@ import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UiThread; import android.app.PendingIntent; +import android.app.jank.AppJankStats; +import android.app.jank.JankTracker; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.ClipData; @@ -34415,4 +34417,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean getSelfRequestedFrameRateFlag() { return (mPrivateFlags4 & PFLAG4_SELF_REQUESTED_FRAME_RATE) != 0; } + + /** + * Called from apps when they want to report jank stats to the system. + * @param appJankStats the stats that will be merged with the stats collected by the system. + */ + @FlaggedApi(android.app.jank.Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void reportAppJankStats(@NonNull AppJankStats appJankStats) { + getRootView().reportAppJankStats(appJankStats); + } + + /** + * Called by widgets to get a reference to JankTracker in order to update states. + * @hide + */ + public @Nullable JankTracker getJankTracker() { + return getRootView().getJankTracker(); + } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index bd746d5ecf04..270cf085b06f 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -46,9 +46,13 @@ import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.WindowConfiguration; +import android.app.jank.AppJankStats; +import android.app.jank.JankTracker; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; @@ -283,6 +287,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private final WearGestureInterceptionDetector mWearGestureInterceptionDetector; + @Nullable + private AppJankStatsCallback mAppJankStatsCallback; + DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { super(context); @@ -2336,6 +2343,38 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + public interface AppJankStatsCallback { + /** + * Called when app jank stats are being reported to the platform or when a widget needs + * to obtain a reference to the JankTracker instance to update states. + */ + JankTracker getAppJankTracker(); + } + + public void setAppJankStatsCallback(AppJankStatsCallback + jankStatsReportedCallback) { + mAppJankStatsCallback = jankStatsReportedCallback; + } + + @Override + @FlaggedApi(android.app.jank.Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void reportAppJankStats(@NonNull AppJankStats appJankStats) { + if (mAppJankStatsCallback != null) { + JankTracker jankTracker = mAppJankStatsCallback.getAppJankTracker(); + if (jankTracker != null) { + jankTracker.mergeAppJankStats(appJankStats); + } + } + } + + @Override + public @Nullable JankTracker getJankTracker() { + if (mAppJankStatsCallback != null) { + return mAppJankStatsCallback.getAppJankTracker(); + } + return null; + } + @Override public String toString() { return super.toString() + "[" + getTitleSuffix(mWindow.getAttributes()) + "]"; diff --git a/tests/AppJankTest/AndroidManifest.xml b/tests/AppJankTest/AndroidManifest.xml index ae973393b90e..abed1798c47c 100644 --- a/tests/AppJankTest/AndroidManifest.xml +++ b/tests/AppJankTest/AndroidManifest.xml @@ -18,7 +18,7 @@ - +