Add JankData Processing Logic
This change adds the frame attribution and accounting logic. We will
receive batches of JankData which are previously rendered frames that
will have a jank determination associated with it. At a high level the
determinations indicate whether that particular frame was janky or not.
The changes added here will take those determinations and associate them
to widget states. Each widget state has a vsyncid start and end value
indicating the timeframe for which the state was active. Each frame in
the batch will be processed by looking at its associated vsyncid and
determining which states were active for that particular frame.
A running count of each state and how many frames were rendered
while that state was active will be maintained. After ten batches have
been processed the states and frame counts will be reported to the
platform.
Bug: 369679146
Test: atest CoreAppJankTestCases:JankDataProcessorTest
Flag: android.app.jank.detailed_app_jank_metrics_api
Change-Id: I7be13039cbad1b533f4439c3761957411e3dbb10
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
new file mode 100644
index 0000000..981a916
--- /dev/null
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -0,0 +1,351 @@
+/*
+ * 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.app.jank.StateTracker.StateData;
+import android.util.Log;
+import android.util.Pools.SimplePool;
+import android.view.SurfaceControl.JankData;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class is responsible for associating frames received from SurfaceFlinger to active widget
+ * states and logging those states back to the platform.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankDataProcessor {
+
+ private static final int MAX_IN_MEMORY_STATS = 25;
+ private static final int LOG_BATCH_FREQUENCY = 50;
+ private int mCurrentBatchCount = 0;
+ private StateTracker mStateTracker = null;
+ private ArrayList<StateData> mPendingStates = new ArrayList<>();
+ private SimplePool<PendingJankStat> mPendingJankStatsPool =
+ new SimplePool<>(MAX_IN_MEMORY_STATS);
+ private HashMap<String, PendingJankStat> mPendingJankStats = new HashMap<>();
+
+ public JankDataProcessor(@NonNull StateTracker stateTracker) {
+ mStateTracker = stateTracker;
+ }
+
+ /**
+ * Called once per batch of JankData.
+ * @param jankData data received from SurfaceFlinger to be processed
+ * @param activityName name of the activity that is tracking jank metrics.
+ * @param appUid the uid of the app.
+ */
+ public void processJankData(List<JankData> jankData, String activityName, int appUid) {
+ mCurrentBatchCount++;
+ // add all the previous and active states to the pending states list.
+ mStateTracker.retrieveAllStates(mPendingStates);
+
+ // TODO b/376332122 Look to see if this logic can be optimized.
+ for (int i = 0; i < jankData.size(); i++) {
+ JankData frame = jankData.get(i);
+ // for each frame we need to check if the state was active during that time.
+ for (int j = 0; j < mPendingStates.size(); j++) {
+ StateData pendingState = mPendingStates.get(j);
+ // This state was active during the frame
+ if (frame.frameVsyncId >= pendingState.mVsyncIdStart
+ && frame.frameVsyncId <= pendingState.mVsyncIdEnd) {
+ recordFrameCount(frame, pendingState, activityName, appUid);
+
+ pendingState.mProcessed = true;
+ }
+ }
+ }
+ // At this point we have attributed all frames to a state.
+ if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
+ logMetricCounts();
+ }
+ // return the StatData object back to the pool to be reused.
+ jankDataProcessingComplete();
+ }
+
+ /**
+ * Returns the aggregate map of different pending jank stats.
+ */
+ @VisibleForTesting
+ public HashMap<String, PendingJankStat> getPendingJankStats() {
+ return mPendingJankStats;
+ }
+
+ private void jankDataProcessingComplete() {
+ mStateTracker.stateProcessingComplete();
+ mPendingStates.clear();
+ }
+
+ /**
+ * Determine if frame is Janky and add to existing memory counter or create a new one.
+ */
+ private void recordFrameCount(JankData frameData, StateData stateData, String activityName,
+ int appUid) {
+ // Check if we have an existing Jank state
+ PendingJankStat jankStats = mPendingJankStats.get(stateData.mStateDataKey);
+
+ if (jankStats == null) {
+ // Check if we have space for another pending stat
+ if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) {
+ return;
+ }
+
+ jankStats = mPendingJankStatsPool.acquire();
+ if (jankStats == null) {
+ jankStats = new PendingJankStat();
+ }
+ jankStats.clearStats();
+ jankStats.mActivityName = activityName;
+ jankStats.mUid = appUid;
+ mPendingJankStats.put(stateData.mStateDataKey, jankStats);
+ }
+ // This state has already been accounted for
+ if (jankStats.processedVsyncId == frameData.frameVsyncId) return;
+
+ jankStats.mTotalFrames += 1;
+ if (frameData.jankType == JankData.JANK_APPLICATION) {
+ jankStats.mJankyFrames += 1;
+ }
+ jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs);
+ jankStats.processedVsyncId = frameData.frameVsyncId;
+
+ }
+
+ /**
+ * When called will log pending Jank stats currently stored in memory to the platform. Will not
+ * clear any pending widget states.
+ */
+ public void logMetricCounts() {
+ //TODO b/374607503 when api changes are in add enum mapping for category and state.
+
+ try {
+ mPendingJankStats.values().forEach(stat -> {
+ FrameworkStatsLog.write(FrameworkStatsLog.JANK_FRAME_COUNT_BY_WIDGET,
+ /*app uid*/ stat.getUid(),
+ /*activity name*/ stat.getActivityName(),
+ /*widget id*/ stat.getWidgetId(),
+ /*refresh rate*/ stat.getRefreshRate(),
+ /*widget category*/ 0,
+ /*widget state*/ 0,
+ /*total frames*/ stat.getTotalFrames(),
+ /*janky frames*/ stat.getJankyFrames(),
+ /*histogram*/ stat.mFrameOverrunBuckets);
+ Log.d(stat.mActivityName, stat.toString());
+ // return the pending stat to the pool it will be reset the next time its
+ // used.
+ mPendingJankStatsPool.release(stat);
+ }
+ );
+ // All stats have been recorded and added back to the pool for reuse, clear the pending
+ // stats.
+ mPendingJankStats.clear();
+ mCurrentBatchCount = 0;
+ } catch (Exception exception) {
+ // TODO b/374608358 handle logging exceptions.
+ }
+ }
+
+ public static final class PendingJankStat {
+ private static final int NANOS_PER_MS = 1000000;
+ public long processedVsyncId = -1;
+
+ // UID of the app
+ private int mUid;
+
+ // The name of the activity that is currently collecting frame metrics.
+ private String mActivityName;
+
+ // 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;
+
+ private int mRefreshRate;
+
+ 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
+ };
+ private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length];
+
+ // Histogram of frame duration overruns encoded in predetermined buckets.
+ public PendingJankStat() {
+ }
+ public long getProcessedVsyncId() {
+ return processedVsyncId;
+ }
+
+ public void setProcessedVsyncId(long processedVsyncId) {
+ this.processedVsyncId = processedVsyncId;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public void setUid(int uid) {
+ mUid = uid;
+ }
+
+ public String getActivityName() {
+ return mActivityName;
+ }
+
+ public void setActivityName(String activityName) {
+ mActivityName = activityName;
+ }
+
+ public String getWidgetId() {
+ return mWidgetId;
+ }
+
+ public void setWidgetId(String widgetId) {
+ mWidgetId = widgetId;
+ }
+
+ public String getWidgetCategory() {
+ return mWidgetCategory;
+ }
+
+ public void setWidgetCategory(String widgetCategory) {
+ mWidgetCategory = widgetCategory;
+ }
+
+ public String getWidgetState() {
+ return mWidgetState;
+ }
+
+ public void setWidgetState(String widgetState) {
+ mWidgetState = widgetState;
+ }
+
+ public long getTotalFrames() {
+ return mTotalFrames;
+ }
+
+ public void setTotalFrames(long totalFrames) {
+ mTotalFrames = totalFrames;
+ }
+
+ public long getJankyFrames() {
+ return mJankyFrames;
+ }
+
+ public void setJankyFrames(long jankyFrames) {
+ mJankyFrames = jankyFrames;
+ }
+
+ public int[] getFrameOverrunBuckets() {
+ return mFrameOverrunBuckets;
+ }
+
+ public int getRefreshRate() {
+ return mRefreshRate;
+ }
+
+ public void setRefreshRate(int refreshRate) {
+ mRefreshRate = refreshRate;
+ }
+
+ /**
+ * Will convert the frame time from ns to ms and record how long the frame took to render.
+ */
+ public void recordFrameOverrun(long frameTimeNano) {
+ try {
+ // TODO b/375650163 calculate frame overrun from refresh rate.
+ int frameTimeMillis = (int) frameTimeNano / NANOS_PER_MS;
+ mFrameOverrunBuckets[indexForFrameOverrun(frameTimeMillis)]++;
+ } catch (IndexOutOfBoundsException exception) {
+ // TODO b/375650163 figure out how to handle this if it happens.
+ }
+ }
+
+ /**
+ * resets all fields in the object back to defaults.
+ */
+ public void clearStats() {
+ this.mUid = -1;
+ this.mActivityName = "";
+ this.processedVsyncId = -1;
+ this.mJankyFrames = 0;
+ this.mTotalFrames = 0;
+ this.mWidgetCategory = "";
+ this.mWidgetState = "";
+ this.mRefreshRate = 0;
+ clearHistogram();
+ }
+
+ private void clearHistogram() {
+ for (int i = 0; i < mFrameOverrunBuckets.length; i++) {
+ mFrameOverrunBuckets[i] = 0;
+ }
+ }
+
+ // This takes the overrun time and returns what bucket it belongs to in the histogram.
+ private int indexForFrameOverrun(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 sFrameOverrunHistogramBounds.length - 1;
+ }
+
+ }
+}
diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java
index cb457ff..c86d5a5 100644
--- a/core/java/android/app/jank/StateTracker.java
+++ b/core/java/android/app/jank/StateTracker.java
@@ -36,7 +36,6 @@
* @hide
*/
@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
-@VisibleForTesting
public class StateTracker {
// Used to synchronize access to mPreviousStates.
@@ -188,7 +187,6 @@
/**
* @hide
*/
- @VisibleForTesting
public static class StateData {
// Concatenated string of widget category, widget state and widget id.
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
new file mode 100644
index 0000000..2cd625e
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankDataProcessor;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class JankDataProcessorTest {
+
+ private Choreographer mChoreographer;
+ private StateTracker mStateTracker;
+ private JankDataProcessor mJankDataProcessor;
+ private static final int NANOS_PER_MS = 1_000_000;
+ private static String sActivityName;
+ private static ActivityScenario<EmptyActivity> sEmptyActivityActivityScenario;
+ private static final int APP_ID = 25;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityActivityScenario = ActivityScenario.launch(EmptyActivity.class);
+ sActivityName = sEmptyActivityActivityScenario.toString();
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityActivityScenario.close();
+ }
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mStateTracker = new StateTracker(mChoreographer);
+ mJankDataProcessor = new JankDataProcessor(mStateTracker);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_multipleFramesAndStates_attributesTotalFramesCorrectly() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ long totalFramesAttributed = getTotalFramesCounted();
+
+ // Each state is active for each frame that is passed in, there are two states being tested
+ // which is why jankData.size is multiplied by 2.
+ assertEquals(jankData.size() * 2, totalFramesAttributed);
+ }
+
+ /**
+ * Each JankData frame has an associated vsyncid, only frames that have vsyncids between the
+ * StatData start and end vsyncids should be counted. This test confirms that if JankData
+ * does not share any frames with the states then no jank stats are added.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_outOfRangeVsyncId_skipOutOfRangeVsyncIds() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_outOfRange());
+
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ assertEquals(0, mJankDataProcessor.getPendingJankStats().size());
+ }
+
+ /**
+ * It's expected to see many duplicate widget states, if a user is scrolling then
+ * pauses and resumes scrolling again, we may get three widget states two of which are the same.
+ * State 1: {Scroll,WidgetId,Scrolling} State 2: {Scroll,WidgetId,None}
+ * State 3: {Scroll,WidgetId,Scrolling}
+ * These duplicate states should coalesce into only one Jank stat. This test confirms that
+ * behavior.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_duplicateStates_confirmDuplicatesCoalesce() {
+ // getMockStateData will return 10 states 5 of which are set to none and 5 of which are
+ // scrolling.
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+ mJankDataProcessor.processJankData(getMockJankData_vsyncId_inRange(), sActivityName,
+ APP_ID);
+
+ // Confirm the duplicate states are coalesced down to 2 stats 1 for the scrolling state
+ // another for the none state.
+ assertEquals(2, mJankDataProcessor.getPendingJankStats().size());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_inRangeVsyncIds_confirmOnlyInRangeFramesCounted() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ int inRangeFrameCount = jankData.size();
+
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ // Two states are active for each frame which is why inRangeFrameCount is multiplied by 2.
+ assertEquals(inRangeFrameCount * 2, getTotalFramesCounted());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_inRangeVsyncIds_confirmHistogramCountMatchesFrameCount() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ long totalFrames = getTotalFramesCounted();
+ long histogramFrames = getHistogramFrameCount();
+
+ assertEquals(totalFrames, histogramFrames);
+ }
+
+ // TODO b/375005277 add tests that cover logging and releasing resources back to pool.
+
+ private long getTotalFramesCounted() {
+ return mJankDataProcessor.getPendingJankStats().values()
+ .stream().mapToLong(stat -> stat.getTotalFrames()).sum();
+ }
+
+ private long getHistogramFrameCount() {
+ long totalHistogramFrames = 0;
+
+ for (JankDataProcessor.PendingJankStat stats :
+ mJankDataProcessor.getPendingJankStats().values()) {
+ int[] overrunHistogram = stats.getFrameOverrunBuckets();
+
+ for (int i = 0; i < overrunHistogram.length; i++) {
+ totalHistogramFrames += overrunHistogram[i];
+ }
+ }
+
+ return totalHistogramFrames;
+ }
+
+ /**
+ * Out of range data will have a mVsyncIdStart and mVsyncIdEnd values set to below 25.
+ */
+ private List<StateTracker.StateData> getMockStateData_vsyncId_outOfRange() {
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+ StateTracker.StateData newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = 20;
+ newStateData.mStateDataKey = "Test1_OutBand";
+ newStateData.mVsyncIdStart = 1;
+ newStateData.mWidgetState = "scrolling";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+ stateData.add(newStateData);
+
+ newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = 24;
+ newStateData.mStateDataKey = "Test1_InBand";
+ newStateData.mVsyncIdStart = 20;
+ newStateData.mWidgetState = "Idle";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+ stateData.add(newStateData);
+
+ newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = 20;
+ newStateData.mStateDataKey = "Test1_OutBand";
+ newStateData.mVsyncIdStart = 12;
+ newStateData.mWidgetState = "Idle";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+ stateData.add(newStateData);
+
+ return stateData;
+ }
+
+ /**
+ * This method returns two unique states, one state is set to scrolling the other is set
+ * to none. Both states will have the same startvsyncid to ensure each state is counted the same
+ * number of times. This keeps logic in asserts easier to reason about. Both states will have
+ * a startVsyncId between 25 and 35.
+ */
+ private List<StateTracker.StateData> getMockStateData_vsyncId_inRange() {
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+
+ for (int i = 0; i < 10; i++) {
+ StateTracker.StateData newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = Long.MAX_VALUE;
+ newStateData.mStateDataKey = "Test1_" + (i % 2 == 0 ? "scrolling" : "none");
+ // Divide i by two to ensure both the scrolling and none states get the same vsyncid
+ // This makes asserts in tests easier to reason about as each state should be counted
+ // the same number of times.
+ newStateData.mVsyncIdStart = 25 + (i / 2);
+ newStateData.mWidgetState = i % 2 == 0 ? "scrolling" : "none";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+
+ stateData.add(newStateData);
+ }
+
+ return stateData;
+ }
+
+ /**
+ * In range data will have a frameVsyncId value between 25 and 35.
+ */
+ private List<SurfaceControl.JankData> getMockJankData_vsyncId_inRange() {
+ ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ mockData.add(new SurfaceControl.JankData(
+ /*frameVsyncId*/25 + i,
+ SurfaceControl.JankData.JANK_NONE,
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i)));
+
+ }
+
+ return mockData;
+ }
+
+ /**
+ * Out of range data will have frameVsyncId values below 25.
+ */
+ private List<SurfaceControl.JankData> getMockJankData_vsyncId_outOfRange() {
+ ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ mockData.add(new SurfaceControl.JankData(
+ /*frameVsyncId*/i,
+ SurfaceControl.JankData.JANK_NONE,
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i)));
+
+ }
+
+ return mockData;
+ }
+
+}