Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 74c60064 authored by Steven Terrell's avatar Steven Terrell
Browse files

Incorporate API feedback

This change addresses the feedback received from the api council. It
addresses the following:
     * Adds more documentation around widgetCategory
     * Adds the WIDGET_CATEGORY prefix to the StringDefs
     * Add more documentation around RelativeFrameTimeHistrogram and
       why there are negative buckets.
     * adds a MAX bucket to the histogram to mirror the MIN.

Bug: 382041175
Test: Atest CoreAppJankTestCases
Flag: android.app.jank.detailed_app_jank_metrics_api

Change-Id: Ic4e77e08b06fdf03982a5aa1f2b6363d16869205
parent 150c48e4
Loading
Loading
Loading
Loading
+25 −25
Original line number Diff line number Diff line
@@ -9188,37 +9188,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();
  }
+65 −61
Original line number Diff line number Diff line
@@ -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
@@ -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)
@@ -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)
@@ -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;
    }

    /**
@@ -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.
@@ -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.
@@ -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;
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -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) {
@@ -136,7 +136,7 @@ public class JankDataProcessor {
        pendingStat.mJankyFrames = jankStats.getJankyFrameCount();

        mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
                jankStats.getFrameOverrunHistogram().getBucketCounters());
                jankStats.getRelativeFrameTimeHistogram().getBucketCounters());

        mPendingJankStats.put(stateKey, pendingStat);
    }
@@ -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];

@@ -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;
+129 −0
Original line number Diff line number Diff line
@@ -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() {
@@ -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.
@@ -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;
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ public class IntegrationTests {
    private ActivityTestRule<EmptyActivity> mEmptyActivityRule =
            new ActivityTestRule<>(EmptyActivity.class, false , true);


    @Before
    public void setUp() {
        mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -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