Loading core/api/current.txt +25 −25 Original line number Diff line number Diff line Loading @@ -9190,37 +9190,37 @@ 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(); ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram); method public long getJankyFrameCount(); method @NonNull public android.app.jank.RelativeFrameTimeHistogram getRelativeFrameTimeHistogram(); 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); field public static final String WIDGET_CATEGORY_ANIMATION = "animation"; field public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard"; field public static final String WIDGET_CATEGORY_MEDIA = "media"; field public static final String WIDGET_CATEGORY_NAVIGATION = "navigation"; field public static final String WIDGET_CATEGORY_OTHER = "other"; field public static final String WIDGET_CATEGORY_SCROLL = "scroll"; field public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; field public static final String WIDGET_STATE_ANIMATING = "animating"; field public static final String WIDGET_STATE_DRAGGING = "dragging"; field public static final String WIDGET_STATE_FLINGING = "flinging"; field public static final String WIDGET_STATE_NONE = "none"; field public static final String WIDGET_STATE_PLAYBACK = "playback"; field public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back"; field public static final String WIDGET_STATE_SCROLLING = "scrolling"; field public static final String WIDGET_STATE_SWIPING = "swiping"; field public static final String WIDGET_STATE_TAPPING = "tapping"; field public static final String WIDGET_STATE_UNSPECIFIED = "unspecified"; field public static final String WIDGET_STATE_ZOOMING = "zooming"; } @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class RelativeFrameTimeHistogram { ctor public RelativeFrameTimeHistogram(); method public void addRelativeFrameTimeMillis(int); method @NonNull public int[] getBucketCounters(); method @NonNull public int[] getBucketEndpointsMillis(); } core/java/android/app/jank/AppJankStats.java +65 −61 Original line number Diff line number Diff line Loading @@ -41,7 +41,8 @@ public final class AppJankStats { // The id that has been set for the widget. private String mWidgetId; // A general category that the widget applies to. // A general category the widget falls into based on the functions it performs or helps // facilitate. private String mWidgetCategory; // The states that the UI elements can report Loading @@ -53,78 +54,78 @@ public final class AppJankStats { // 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; // Histogram of relative frame times encoded in predetermined buckets. private RelativeFrameTimeHistogram mRelativeFrameTimeHistogram; /** Used to indicate no widget category has been set. */ public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified"; public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; /** UI elements that facilitate scrolling. */ public static final String SCROLL = "scroll"; public static final String WIDGET_CATEGORY_SCROLL = "scroll"; /** UI elements that facilitate playing animations. */ public static final String ANIMATION = "animation"; public static final String WIDGET_CATEGORY_ANIMATION = "animation"; /** UI elements that facilitate media playback. */ public static final String MEDIA = "media"; public static final String WIDGET_CATEGORY_MEDIA = "media"; /** UI elements that facilitate in-app navigation. */ public static final String NAVIGATION = "navigation"; public static final String WIDGET_CATEGORY_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"; public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard"; /** UI elements that don't fall in one or any of the other categories. */ public static final String OTHER = "other"; public static final String WIDGET_CATEGORY_OTHER = "other"; /** Used to indicate no widget state has been set. */ public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; public static final String WIDGET_STATE_UNSPECIFIED = "unspecified"; /** Used to indicate the UI element currently has no state and is idle. */ public static final String NONE = "none"; public static final String WIDGET_STATE_NONE = "none"; /** Used to indicate the UI element is currently scrolling. */ public static final String SCROLLING = "scrolling"; public static final String WIDGET_STATE_SCROLLING = "scrolling"; /** Used to indicate the UI element is currently being flung. */ public static final String FLINGING = "flinging"; public static final String WIDGET_STATE_FLINGING = "flinging"; /** Used to indicate the UI element is currently being swiped. */ public static final String SWIPING = "swiping"; public static final String WIDGET_STATE_SWIPING = "swiping"; /** Used to indicate the UI element is currently being dragged. */ public static final String DRAGGING = "dragging"; public static final String WIDGET_STATE_DRAGGING = "dragging"; /** Used to indicate the UI element is currently zooming. */ public static final String ZOOMING = "zooming"; public static final String WIDGET_STATE_ZOOMING = "zooming"; /** Used to indicate the UI element is currently animating. */ public static final String ANIMATING = "animating"; public static final String WIDGET_STATE_ANIMATING = "animating"; /** Used to indicate the UI element is currently playing media. */ public static final String PLAYBACK = "playback"; public static final String WIDGET_STATE_PLAYBACK = "playback"; /** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */ public static final String TAPPING = "tapping"; public static final String WIDGET_STATE_TAPPING = "tapping"; /** Used to indicate predictive back navigation is currently being used */ public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back"; /** * Provide an organized way to group widgets that have similar purposes or perform related * functions. * @hide */ @StringDef(value = { @StringDef(prefix = {"WIDGET_CATEGORY_"}, value = { WIDGET_CATEGORY_UNSPECIFIED, SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, PREDICTIVE_BACK, OTHER WIDGET_CATEGORY_SCROLL, WIDGET_CATEGORY_ANIMATION, WIDGET_CATEGORY_MEDIA, WIDGET_CATEGORY_NAVIGATION, WIDGET_CATEGORY_KEYBOARD, WIDGET_CATEGORY_OTHER }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.SOURCE) Loading @@ -133,17 +134,18 @@ public final class AppJankStats { /** * @hide */ @StringDef(value = { @StringDef(prefix = {"WIDGET_STATE_"}, value = { WIDGET_STATE_UNSPECIFIED, NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING, PLAYBACK, TAPPING, WIDGET_STATE_NONE, WIDGET_STATE_SCROLLING, WIDGET_STATE_FLINGING, WIDGET_STATE_SWIPING, WIDGET_STATE_DRAGGING, WIDGET_STATE_ZOOMING, WIDGET_STATE_ANIMATING, WIDGET_STATE_PLAYBACK, WIDGET_STATE_TAPPING, WIDGET_STATE_PREDICTIVE_BACK }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.SOURCE) Loading @@ -156,31 +158,33 @@ public final class AppJankStats { * * @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 widgetCategory a category used to organize widgets in a structured way that indicates * they serve a similar purpose or perform related functions. Must be * prefixed with WIDGET_CATEGORY_ and have a suffix of one of the * following:SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, OTHER or * will be set to UNSPECIFIED if no value is passed. * @param widgetState the state the widget was in while frames were counted. Must be prefixed * with WIDGET_STATE_ and have a suffix of one of the following: * NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING, * PLAYBACK, TAPPING, PREDICTIVE_BACK 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. * @param relativeFrameTimeHistogram the histogram with predefined buckets. See * {@link #getRelativeFrameTimeHistogram()} for details. * */ public AppJankStats(int appUid, @NonNull String widgetId, @Nullable @WidgetCategory String widgetCategory, @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames, @NonNull FrameOverrunHistogram frameOverrunHistogram) { @NonNull RelativeFrameTimeHistogram relativeFrameTimeHistogram) { mUid = appUid; mWidgetId = widgetId; mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED; mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED; mTotalFrames = totalFrames; mJankyFrames = jankyFrames; mFrameOverrunHistogram = frameOverrunHistogram; mRelativeFrameTimeHistogram = relativeFrameTimeHistogram; } /** Loading @@ -203,7 +207,7 @@ public final class AppJankStats { /** * 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. * {@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. Loading @@ -213,7 +217,7 @@ public final class AppJankStats { } /** * Returns the widget's state that was reported for this stat, or widget_state_unspecified * Returns the widget's state that was reported for this stat, or * {@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. Loading Loading @@ -241,13 +245,13 @@ public final class AppJankStats { } /** * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets. * See {@link FrameOverrunHistogram} for more information. * Returns a Histogram containing relative frame times in millis grouped into predefined * buckets. See {@link RelativeFrameTimeHistogram} for more information. * * @return Histogram containing frame overrun times in predefined buckets. This value cannot * @return Histogram containing relative frame times in predefined buckets. This value cannot * be null. */ public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() { return mFrameOverrunHistogram; public @NonNull RelativeFrameTimeHistogram getRelativeFrameTimeHistogram() { return mRelativeFrameTimeHistogram; } } core/java/android/app/jank/JankDataProcessor.java +5 −4 Original line number Diff line number Diff line Loading @@ -111,7 +111,7 @@ public class JankDataProcessor { pendingStat.mTotalFrames += jankStat.getTotalFrameCount(); mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, jankStat.getFrameOverrunHistogram().getBucketCounters()); jankStat.getRelativeFrameTimeHistogram().getBucketCounters()); } private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) { Loading @@ -136,7 +136,7 @@ public class JankDataProcessor { pendingStat.mJankyFrames = jankStats.getJankyFrameCount(); mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, jankStats.getFrameOverrunHistogram().getBucketCounters()); jankStats.getRelativeFrameTimeHistogram().getBucketCounters()); mPendingJankStats.put(stateKey, pendingStat); } Loading Loading @@ -271,7 +271,8 @@ public class JankDataProcessor { private static final int[] sFrameOverrunHistogramBounds = { 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 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, Integer.MAX_VALUE }; private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length]; Loading Loading @@ -414,7 +415,7 @@ public class JankDataProcessor { if (overrunTime < 200) { return (overrunTime - 50) / 100 + 41; } if (overrunTime < 1000) { if (overrunTime <= 1000) { return (overrunTime - 200) / 100 + 43; } return sFrameOverrunHistogramBounds.length - 1; Loading core/java/android/app/jank/FrameOverrunHistogram.java→core/java/android/app/jank/RelativeFrameTimeHistogram.java +129 −0 Original line number Diff line number Diff line Loading @@ -22,41 +22,58 @@ 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. * A histogram of frame times relative to their deadline. * * This class aids in reporting {@link AppJankStats} to the system and is designed for use by * library widgets. It facilitates the recording of frame times in relation to the frame deadline. * The class records the distribution of time remaining until a frame is considered janky or how * janky the frame was. * <p> * A frame's relative frame time value indicates whether it was delivered early, on time, or late. * A negative relative frame time value indicates the frame was delivered early, a value of zero * indicates the frame was delivered on time and a positive value indicates the frame was delivered * late. The values of the endpoints indicate how early or late a frame was delivered. * <p> * The relative frame times are recorded as a histogram: values are * {@link #addRelativeFrameTimeMillis added} to a bucket by increasing the bucket's counter. The * count of frames with a relative frame time between * {@link #getBucketEndpointsMillis bucket endpoints} {@code i} and {@code i+1} can be obtained * through index {@code i} of {@link #getBucketCounters}. * */ @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) public class FrameOverrunHistogram { public class RelativeFrameTimeHistogram { 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 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, Integer.MAX_VALUE }; // private int[] mBucketCounts; /** * Create a new instance of FrameOverrunHistogram. * Create a new instance of RelativeFrameTimeHistogram. */ public FrameOverrunHistogram() { mBucketCounts = new int[sBucketEndpoints.length]; public RelativeFrameTimeHistogram() { mBucketCounts = new int[sBucketEndpoints.length - 1]; } /** * Increases the count by one for the bucket representing the frame overrun duration. * Increases the count by one for the bucket representing the relative frame time. * * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference * @param frameTimeMillis relative frame time in millis, relative frame time is the difference * between a frames deadline and when it was rendered. */ public void addFrameOverrunMillis(int frameOverrunMillis) { int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis); public void addRelativeFrameTimeMillis(int frameTimeMillis) { int countsIndex = getRelativeFrameTimeBucketIndex(frameTimeMillis); mBucketCounts[countsIndex]++; } /** * Returns the counts for the all the frame overrun buckets. * Returns the counts for the all the relative frame time buckets. * * @return an array of integers representing the counts of frame overrun times. This value * @return an array of integers representing the counts of relative frame times. This value * cannot be null. */ public @NonNull int[] getBucketCounters() { Loading @@ -64,7 +81,11 @@ public class FrameOverrunHistogram { } /** * Returns the predefined endpoints for the histogram. * Returns the relative frame time endpoints for the histogram. * <p> * Index {@code i} of {@link #getBucketCounters} contains the count of frames that had a * relative frame time between {@code endpoints[i]} (inclusive) and {@code endpoints[i+1]} * (exclusive). * * @return array of integers representing the endpoints for the predefined histogram count * buckets. This value cannot be null. Loading @@ -73,35 +94,36 @@ public class FrameOverrunHistogram { 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; // This takes the relative frame time and returns what bucket it belongs to in the counters // array. private int getRelativeFrameTimeBucketIndex(int relativeFrameTime) { if (relativeFrameTime < 20) { if (relativeFrameTime >= -20) { return (relativeFrameTime + 20) / 2 + 12; } if (overrunTime >= -30) { return (overrunTime + 30) / 5 + 10; if (relativeFrameTime >= -30) { return (relativeFrameTime + 30) / 5 + 10; } if (overrunTime >= -100) { return (overrunTime + 100) / 10 + 3; if (relativeFrameTime >= -100) { return (relativeFrameTime + 100) / 10 + 3; } if (overrunTime >= -200) { return (overrunTime + 200) / 50 + 1; if (relativeFrameTime >= -200) { return (relativeFrameTime + 200) / 50 + 1; } return 0; } if (overrunTime < 30) { return (overrunTime - 20) / 5 + 32; if (relativeFrameTime < 30) { return (relativeFrameTime - 20) / 5 + 32; } if (overrunTime < 100) { return (overrunTime - 30) / 10 + 34; if (relativeFrameTime < 100) { return (relativeFrameTime - 30) / 10 + 34; } if (overrunTime < 200) { return (overrunTime - 50) / 100 + 41; if (relativeFrameTime < 200) { return (relativeFrameTime - 50) / 100 + 41; } if (overrunTime < 1000) { return (overrunTime - 200) / 100 + 43; if (relativeFrameTime < 1000) { return (relativeFrameTime - 200) / 100 + 43; } return sBucketEndpoints.length - 1; return mBucketCounts.length - 1; } } tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java +2 −1 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ public class IntegrationTests { private ActivityTestRule<EmptyActivity> mEmptyActivityRule = new ActivityTestRule<>(EmptyActivity.class, false , true); @Before public void setUp() { mInstrumentation = InstrumentationRegistry.getInstrumentation(); Loading Loading @@ -163,7 +164,7 @@ public class IntegrationTests { // of that state. for (int i = 0; i < uiStates.size(); i++) { StateTracker.StateData stateData = uiStates.get(i); if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) { if (stateData.mWidgetCategory.equals(AppJankStats.WIDGET_CATEGORY_ANIMATION)) { assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd); } } Loading Loading
core/api/current.txt +25 −25 Original line number Diff line number Diff line Loading @@ -9190,37 +9190,37 @@ 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(); ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram); method public long getJankyFrameCount(); method @NonNull public android.app.jank.RelativeFrameTimeHistogram getRelativeFrameTimeHistogram(); 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); field public static final String WIDGET_CATEGORY_ANIMATION = "animation"; field public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard"; field public static final String WIDGET_CATEGORY_MEDIA = "media"; field public static final String WIDGET_CATEGORY_NAVIGATION = "navigation"; field public static final String WIDGET_CATEGORY_OTHER = "other"; field public static final String WIDGET_CATEGORY_SCROLL = "scroll"; field public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; field public static final String WIDGET_STATE_ANIMATING = "animating"; field public static final String WIDGET_STATE_DRAGGING = "dragging"; field public static final String WIDGET_STATE_FLINGING = "flinging"; field public static final String WIDGET_STATE_NONE = "none"; field public static final String WIDGET_STATE_PLAYBACK = "playback"; field public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back"; field public static final String WIDGET_STATE_SCROLLING = "scrolling"; field public static final String WIDGET_STATE_SWIPING = "swiping"; field public static final String WIDGET_STATE_TAPPING = "tapping"; field public static final String WIDGET_STATE_UNSPECIFIED = "unspecified"; field public static final String WIDGET_STATE_ZOOMING = "zooming"; } @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class RelativeFrameTimeHistogram { ctor public RelativeFrameTimeHistogram(); method public void addRelativeFrameTimeMillis(int); method @NonNull public int[] getBucketCounters(); method @NonNull public int[] getBucketEndpointsMillis(); }
core/java/android/app/jank/AppJankStats.java +65 −61 Original line number Diff line number Diff line Loading @@ -41,7 +41,8 @@ public final class AppJankStats { // The id that has been set for the widget. private String mWidgetId; // A general category that the widget applies to. // A general category the widget falls into based on the functions it performs or helps // facilitate. private String mWidgetCategory; // The states that the UI elements can report Loading @@ -53,78 +54,78 @@ public final class AppJankStats { // 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; // Histogram of relative frame times encoded in predetermined buckets. private RelativeFrameTimeHistogram mRelativeFrameTimeHistogram; /** Used to indicate no widget category has been set. */ public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified"; public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; /** UI elements that facilitate scrolling. */ public static final String SCROLL = "scroll"; public static final String WIDGET_CATEGORY_SCROLL = "scroll"; /** UI elements that facilitate playing animations. */ public static final String ANIMATION = "animation"; public static final String WIDGET_CATEGORY_ANIMATION = "animation"; /** UI elements that facilitate media playback. */ public static final String MEDIA = "media"; public static final String WIDGET_CATEGORY_MEDIA = "media"; /** UI elements that facilitate in-app navigation. */ public static final String NAVIGATION = "navigation"; public static final String WIDGET_CATEGORY_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"; public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard"; /** UI elements that don't fall in one or any of the other categories. */ public static final String OTHER = "other"; public static final String WIDGET_CATEGORY_OTHER = "other"; /** Used to indicate no widget state has been set. */ public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; public static final String WIDGET_STATE_UNSPECIFIED = "unspecified"; /** Used to indicate the UI element currently has no state and is idle. */ public static final String NONE = "none"; public static final String WIDGET_STATE_NONE = "none"; /** Used to indicate the UI element is currently scrolling. */ public static final String SCROLLING = "scrolling"; public static final String WIDGET_STATE_SCROLLING = "scrolling"; /** Used to indicate the UI element is currently being flung. */ public static final String FLINGING = "flinging"; public static final String WIDGET_STATE_FLINGING = "flinging"; /** Used to indicate the UI element is currently being swiped. */ public static final String SWIPING = "swiping"; public static final String WIDGET_STATE_SWIPING = "swiping"; /** Used to indicate the UI element is currently being dragged. */ public static final String DRAGGING = "dragging"; public static final String WIDGET_STATE_DRAGGING = "dragging"; /** Used to indicate the UI element is currently zooming. */ public static final String ZOOMING = "zooming"; public static final String WIDGET_STATE_ZOOMING = "zooming"; /** Used to indicate the UI element is currently animating. */ public static final String ANIMATING = "animating"; public static final String WIDGET_STATE_ANIMATING = "animating"; /** Used to indicate the UI element is currently playing media. */ public static final String PLAYBACK = "playback"; public static final String WIDGET_STATE_PLAYBACK = "playback"; /** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */ public static final String TAPPING = "tapping"; public static final String WIDGET_STATE_TAPPING = "tapping"; /** Used to indicate predictive back navigation is currently being used */ public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back"; /** * Provide an organized way to group widgets that have similar purposes or perform related * functions. * @hide */ @StringDef(value = { @StringDef(prefix = {"WIDGET_CATEGORY_"}, value = { WIDGET_CATEGORY_UNSPECIFIED, SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, PREDICTIVE_BACK, OTHER WIDGET_CATEGORY_SCROLL, WIDGET_CATEGORY_ANIMATION, WIDGET_CATEGORY_MEDIA, WIDGET_CATEGORY_NAVIGATION, WIDGET_CATEGORY_KEYBOARD, WIDGET_CATEGORY_OTHER }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.SOURCE) Loading @@ -133,17 +134,18 @@ public final class AppJankStats { /** * @hide */ @StringDef(value = { @StringDef(prefix = {"WIDGET_STATE_"}, value = { WIDGET_STATE_UNSPECIFIED, NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING, PLAYBACK, TAPPING, WIDGET_STATE_NONE, WIDGET_STATE_SCROLLING, WIDGET_STATE_FLINGING, WIDGET_STATE_SWIPING, WIDGET_STATE_DRAGGING, WIDGET_STATE_ZOOMING, WIDGET_STATE_ANIMATING, WIDGET_STATE_PLAYBACK, WIDGET_STATE_TAPPING, WIDGET_STATE_PREDICTIVE_BACK }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.SOURCE) Loading @@ -156,31 +158,33 @@ public final class AppJankStats { * * @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 widgetCategory a category used to organize widgets in a structured way that indicates * they serve a similar purpose or perform related functions. Must be * prefixed with WIDGET_CATEGORY_ and have a suffix of one of the * following:SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, OTHER or * will be set to UNSPECIFIED if no value is passed. * @param widgetState the state the widget was in while frames were counted. Must be prefixed * with WIDGET_STATE_ and have a suffix of one of the following: * NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING, * PLAYBACK, TAPPING, PREDICTIVE_BACK 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. * @param relativeFrameTimeHistogram the histogram with predefined buckets. See * {@link #getRelativeFrameTimeHistogram()} for details. * */ public AppJankStats(int appUid, @NonNull String widgetId, @Nullable @WidgetCategory String widgetCategory, @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames, @NonNull FrameOverrunHistogram frameOverrunHistogram) { @NonNull RelativeFrameTimeHistogram relativeFrameTimeHistogram) { mUid = appUid; mWidgetId = widgetId; mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED; mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED; mTotalFrames = totalFrames; mJankyFrames = jankyFrames; mFrameOverrunHistogram = frameOverrunHistogram; mRelativeFrameTimeHistogram = relativeFrameTimeHistogram; } /** Loading @@ -203,7 +207,7 @@ public final class AppJankStats { /** * 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. * {@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. Loading @@ -213,7 +217,7 @@ public final class AppJankStats { } /** * Returns the widget's state that was reported for this stat, or widget_state_unspecified * Returns the widget's state that was reported for this stat, or * {@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. Loading Loading @@ -241,13 +245,13 @@ public final class AppJankStats { } /** * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets. * See {@link FrameOverrunHistogram} for more information. * Returns a Histogram containing relative frame times in millis grouped into predefined * buckets. See {@link RelativeFrameTimeHistogram} for more information. * * @return Histogram containing frame overrun times in predefined buckets. This value cannot * @return Histogram containing relative frame times in predefined buckets. This value cannot * be null. */ public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() { return mFrameOverrunHistogram; public @NonNull RelativeFrameTimeHistogram getRelativeFrameTimeHistogram() { return mRelativeFrameTimeHistogram; } }
core/java/android/app/jank/JankDataProcessor.java +5 −4 Original line number Diff line number Diff line Loading @@ -111,7 +111,7 @@ public class JankDataProcessor { pendingStat.mTotalFrames += jankStat.getTotalFrameCount(); mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, jankStat.getFrameOverrunHistogram().getBucketCounters()); jankStat.getRelativeFrameTimeHistogram().getBucketCounters()); } private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) { Loading @@ -136,7 +136,7 @@ public class JankDataProcessor { pendingStat.mJankyFrames = jankStats.getJankyFrameCount(); mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, jankStats.getFrameOverrunHistogram().getBucketCounters()); jankStats.getRelativeFrameTimeHistogram().getBucketCounters()); mPendingJankStats.put(stateKey, pendingStat); } Loading Loading @@ -271,7 +271,8 @@ public class JankDataProcessor { private static final int[] sFrameOverrunHistogramBounds = { 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 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, Integer.MAX_VALUE }; private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length]; Loading Loading @@ -414,7 +415,7 @@ public class JankDataProcessor { if (overrunTime < 200) { return (overrunTime - 50) / 100 + 41; } if (overrunTime < 1000) { if (overrunTime <= 1000) { return (overrunTime - 200) / 100 + 43; } return sFrameOverrunHistogramBounds.length - 1; Loading
core/java/android/app/jank/FrameOverrunHistogram.java→core/java/android/app/jank/RelativeFrameTimeHistogram.java +129 −0 Original line number Diff line number Diff line Loading @@ -22,41 +22,58 @@ 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. * A histogram of frame times relative to their deadline. * * This class aids in reporting {@link AppJankStats} to the system and is designed for use by * library widgets. It facilitates the recording of frame times in relation to the frame deadline. * The class records the distribution of time remaining until a frame is considered janky or how * janky the frame was. * <p> * A frame's relative frame time value indicates whether it was delivered early, on time, or late. * A negative relative frame time value indicates the frame was delivered early, a value of zero * indicates the frame was delivered on time and a positive value indicates the frame was delivered * late. The values of the endpoints indicate how early or late a frame was delivered. * <p> * The relative frame times are recorded as a histogram: values are * {@link #addRelativeFrameTimeMillis added} to a bucket by increasing the bucket's counter. The * count of frames with a relative frame time between * {@link #getBucketEndpointsMillis bucket endpoints} {@code i} and {@code i+1} can be obtained * through index {@code i} of {@link #getBucketCounters}. * */ @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) public class FrameOverrunHistogram { public class RelativeFrameTimeHistogram { 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 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, Integer.MAX_VALUE }; // private int[] mBucketCounts; /** * Create a new instance of FrameOverrunHistogram. * Create a new instance of RelativeFrameTimeHistogram. */ public FrameOverrunHistogram() { mBucketCounts = new int[sBucketEndpoints.length]; public RelativeFrameTimeHistogram() { mBucketCounts = new int[sBucketEndpoints.length - 1]; } /** * Increases the count by one for the bucket representing the frame overrun duration. * Increases the count by one for the bucket representing the relative frame time. * * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference * @param frameTimeMillis relative frame time in millis, relative frame time is the difference * between a frames deadline and when it was rendered. */ public void addFrameOverrunMillis(int frameOverrunMillis) { int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis); public void addRelativeFrameTimeMillis(int frameTimeMillis) { int countsIndex = getRelativeFrameTimeBucketIndex(frameTimeMillis); mBucketCounts[countsIndex]++; } /** * Returns the counts for the all the frame overrun buckets. * Returns the counts for the all the relative frame time buckets. * * @return an array of integers representing the counts of frame overrun times. This value * @return an array of integers representing the counts of relative frame times. This value * cannot be null. */ public @NonNull int[] getBucketCounters() { Loading @@ -64,7 +81,11 @@ public class FrameOverrunHistogram { } /** * Returns the predefined endpoints for the histogram. * Returns the relative frame time endpoints for the histogram. * <p> * Index {@code i} of {@link #getBucketCounters} contains the count of frames that had a * relative frame time between {@code endpoints[i]} (inclusive) and {@code endpoints[i+1]} * (exclusive). * * @return array of integers representing the endpoints for the predefined histogram count * buckets. This value cannot be null. Loading @@ -73,35 +94,36 @@ public class FrameOverrunHistogram { 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; // This takes the relative frame time and returns what bucket it belongs to in the counters // array. private int getRelativeFrameTimeBucketIndex(int relativeFrameTime) { if (relativeFrameTime < 20) { if (relativeFrameTime >= -20) { return (relativeFrameTime + 20) / 2 + 12; } if (overrunTime >= -30) { return (overrunTime + 30) / 5 + 10; if (relativeFrameTime >= -30) { return (relativeFrameTime + 30) / 5 + 10; } if (overrunTime >= -100) { return (overrunTime + 100) / 10 + 3; if (relativeFrameTime >= -100) { return (relativeFrameTime + 100) / 10 + 3; } if (overrunTime >= -200) { return (overrunTime + 200) / 50 + 1; if (relativeFrameTime >= -200) { return (relativeFrameTime + 200) / 50 + 1; } return 0; } if (overrunTime < 30) { return (overrunTime - 20) / 5 + 32; if (relativeFrameTime < 30) { return (relativeFrameTime - 20) / 5 + 32; } if (overrunTime < 100) { return (overrunTime - 30) / 10 + 34; if (relativeFrameTime < 100) { return (relativeFrameTime - 30) / 10 + 34; } if (overrunTime < 200) { return (overrunTime - 50) / 100 + 41; if (relativeFrameTime < 200) { return (relativeFrameTime - 50) / 100 + 41; } if (overrunTime < 1000) { return (overrunTime - 200) / 100 + 43; if (relativeFrameTime < 1000) { return (relativeFrameTime - 200) / 100 + 43; } return sBucketEndpoints.length - 1; return mBucketCounts.length - 1; } }
tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java +2 −1 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ public class IntegrationTests { private ActivityTestRule<EmptyActivity> mEmptyActivityRule = new ActivityTestRule<>(EmptyActivity.class, false , true); @Before public void setUp() { mInstrumentation = InstrumentationRegistry.getInstrumentation(); Loading Loading @@ -163,7 +164,7 @@ public class IntegrationTests { // of that state. for (int i = 0; i < uiStates.size(); i++) { StateTracker.StateData stateData = uiStates.get(i); if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) { if (stateData.mWidgetCategory.equals(AppJankStats.WIDGET_CATEGORY_ANIMATION)) { assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd); } } Loading