Loading core/api/current.txt +41 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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(); core/java/android/app/Activity.java +58 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -1145,6 +1148,9 @@ public class Activity extends ContextThemeWrapper }; @Nullable private JankTracker mJankTracker; private static native String getDlWarning(); /** Loading Loading @@ -2245,6 +2251,10 @@ public class Activity extends ContextThemeWrapper // Notify autofill getAutofillClientController().onActivityPostResumed(); if (android.app.jank.Flags.detailedAppJankMetricsApi()) { startAppJankTracking(); } mCalled = true; } Loading Loading @@ -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(); Loading Loading @@ -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(); } } } core/java/android/app/jank/AppJankStats.java 0 → 100644 +253 −0 Original line number Diff line number Diff line /* * 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; } } core/java/android/app/jank/FrameOverrunHistogram.java 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * 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; } } core/java/android/app/jank/JankDataProcessor.java +8 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading
core/api/current.txt +41 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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();
core/java/android/app/Activity.java +58 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -1145,6 +1148,9 @@ public class Activity extends ContextThemeWrapper }; @Nullable private JankTracker mJankTracker; private static native String getDlWarning(); /** Loading Loading @@ -2245,6 +2251,10 @@ public class Activity extends ContextThemeWrapper // Notify autofill getAutofillClientController().onActivityPostResumed(); if (android.app.jank.Flags.detailedAppJankMetricsApi()) { startAppJankTracking(); } mCalled = true; } Loading Loading @@ -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(); Loading Loading @@ -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(); } } }
core/java/android/app/jank/AppJankStats.java 0 → 100644 +253 −0 Original line number Diff line number Diff line /* * 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; } }
core/java/android/app/jank/FrameOverrunHistogram.java 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * 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; } }
core/java/android/app/jank/JankDataProcessor.java +8 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading