diff options
-rw-r--r-- | core/api/current.txt | 41 | ||||
-rw-r--r-- | core/java/android/app/Activity.java | 58 | ||||
-rw-r--r-- | core/java/android/app/jank/AppJankStats.java | 253 | ||||
-rw-r--r-- | core/java/android/app/jank/FrameOverrunHistogram.java | 107 | ||||
-rw-r--r-- | core/java/android/app/jank/JankDataProcessor.java | 8 | ||||
-rw-r--r-- | core/java/android/app/jank/JankTracker.java | 8 | ||||
-rw-r--r-- | core/java/android/view/View.java | 19 | ||||
-rw-r--r-- | core/java/com/android/internal/policy/DecorView.java | 39 | ||||
-rw-r--r-- | tests/AppJankTest/AndroidManifest.xml | 2 | ||||
-rw-r--r-- | tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java | 15 |
10 files changed, 549 insertions, 1 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 9fc350d75bbc..ed67cc2fe04b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9109,6 +9109,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 { @@ -53738,6 +53778,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 3fccc17e1bf1..419eb7dac5f0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -54,6 +54,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; @@ -124,6 +125,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; @@ -175,6 +177,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; @@ -1145,6 +1148,9 @@ public class Activity extends ContextThemeWrapper }; + @Nullable + private JankTracker mJankTracker; + private static native String getDlWarning(); /** @@ -2245,6 +2251,10 @@ public class Activity extends ContextThemeWrapper // Notify autofill getAutofillClientController().onActivityPostResumed(); + if (android.app.jank.Flags.detailedAppJankMetricsApi()) { + startAppJankTracking(); + } + mCalled = true; } @@ -9264,6 +9274,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(); @@ -9946,4 +9959,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 @@ -87,6 +87,14 @@ public class JankDataProcessor { } /** + * 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. */ @VisibleForTesting 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 fa06831f6514..0b61c94e9bcd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -91,6 +91,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; @@ -34420,4 +34422,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 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.app.jank.tests"> - <application> + <application android:appCategory="news"> <uses-library android:name="android.test.runner" /> <activity android:name=".EmptyActivity" android:label="EmptyActivity" diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java index a3e5533599bc..1bdf019d6c42 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java @@ -17,6 +17,7 @@ package android.app.jank.tests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import android.app.jank.Flags; import android.app.jank.JankTracker; @@ -31,6 +32,8 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ActivityScenario; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.policy.DecorView; + import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -153,4 +156,16 @@ public class JankTrackerTest { assertEquals(1, stateData.size()); } + + /** + * Test confirms a JankTracker object is retrieved from the activity. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_LOGGING_ENABLED) + public void jankTracker_NotNull_WhenRetrievedFromDecorView() { + DecorView decorView = (DecorView) sActivityDecorView; + JankTracker jankTracker = decorView.getJankTracker(); + + assertNotNull(jankTracker); + } } |