summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/jank/StateTracker.java206
-rw-r--r--tests/AppJankTest/Android.bp37
-rw-r--r--tests/AppJankTest/AndroidManifest.xml40
-rw-r--r--tests/AppJankTest/AndroidTest.xml38
-rw-r--r--tests/AppJankTest/OWNERS4
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java22
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java227
7 files changed, 574 insertions, 0 deletions
diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java
new file mode 100644
index 000000000000..cb457ff64430
--- /dev/null
+++ b/core/java/android/app/jank/StateTracker.java
@@ -0,0 +1,206 @@
+/*
+ * 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.util.Pools.SimplePool;
+import android.view.Choreographer;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * StateTracker is responsible for keeping track of currently active states as well as
+ * previously encountered states. States are added, updated or removed by widgets that support state
+ * tracking. When a state is first added it will get a vsyncid associated to it, when that state
+ * is removed or updated to a different state it will have a second vsyncid associated with it. The
+ * two vsyncids create a range of ids where that particular state was active.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+@VisibleForTesting
+public class StateTracker {
+
+ // Used to synchronize access to mPreviousStates.
+ private final Object mLock = new Object();
+ private Choreographer mChoreographer;
+
+ // The max number of StateData objects that will be stored in the pool for reuse.
+ private static final int MAX_POOL_SIZE = 500;
+ // The max number of currently active states to track.
+ protected static final int MAX_CONCURRENT_STATE_COUNT = 25;
+ // The maximum number of previously seen states that will be counted.
+ protected static final int MAX_PREVIOUSLY_ACTIVE_STATE_COUNT = 1000;
+
+ // Pool to store the previously used StateData objects to save recreating them each time.
+ private final SimplePool<StateData> mStateDataObjectPool = new SimplePool<>(MAX_POOL_SIZE);
+ // Previously encountered states that have not been associated to a frame.
+ private ArrayList<StateData> mPreviousStates = new ArrayList<>();
+ // Currently active widgets and widget states
+ private ConcurrentHashMap<String, StateData> mActiveStates = new ConcurrentHashMap<>();
+
+ public StateTracker(@NonNull Choreographer choreographer) {
+ mChoreographer = choreographer;
+ }
+
+ /**
+ * Updates the currentState to the nextState.
+ * @param widgetCategory preselected general widget category.
+ * @param widgetId developer defined widget id if available.
+ * @param currentState current state of the widget.
+ * @param nextState the state the widget will be in.
+ */
+ public void updateState(@NonNull String widgetCategory, @NonNull String widgetId,
+ @NonNull String currentState, @NonNull String nextState) {
+ // remove the now inactive state from the active states list
+ removeState(widgetCategory, widgetId, currentState);
+
+ // add the updated state to the active states list
+ putState(widgetCategory, widgetId, nextState);
+ }
+
+ /**
+ * Removes the state from the active state list and adds it to the previously encountered state
+ * list. Associates an end vsync id to the state.
+ * @param widgetCategory preselected general widget category.
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState no longer active widget state.
+ */
+ public void removeState(@NonNull String widgetCategory, @NonNull String widgetId,
+ @NonNull String widgetState) {
+
+ String stateKey = getStateKey(widgetCategory, widgetId, widgetState);
+ // Check if we have the active state
+ StateData stateData = mActiveStates.remove(stateKey);
+
+ // If there are no states that match just return.
+ // This can happen if mActiveStates is at MAX_CONCURRENT_STATE_COUNT and a widget tries to
+ // remove a state that was never added or if a widget tries to remove the same state twice.
+ if (stateData == null) return;
+
+ synchronized (mLock) {
+ stateData.mVsyncIdEnd = mChoreographer.getVsyncId();
+ // Add the StateData to the previous state list. We need to keep a list of all the
+ // previously active states until we can process the next batch of frame data.
+ if (mPreviousStates.size() < MAX_PREVIOUSLY_ACTIVE_STATE_COUNT) {
+ mPreviousStates.add(stateData);
+ }
+ }
+ }
+
+ /**
+ * Adds a new state to the active state list. Associates a start vsync id to the state.
+ * @param widgetCategory preselected general widget category.
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState the current active widget state.
+ */
+ public void putState(@NonNull String widgetCategory, @NonNull String widgetId,
+ @NonNull String widgetState) {
+
+ // Check if we can accept a new state
+ if (mActiveStates.size() >= MAX_CONCURRENT_STATE_COUNT) return;
+
+ String stateKey = getStateKey(widgetCategory, widgetId, widgetState);
+
+ // Check if there is currently any active states
+ // if there is already a state that matches then its presumed as still active.
+ if (mActiveStates.containsKey(stateKey)) return;
+
+ // Check if we have am unused state object in the pool
+ StateData stateData = mStateDataObjectPool.acquire();
+ if (stateData == null) {
+ stateData = new StateData();
+ }
+ stateData.mVsyncIdStart = mChoreographer.getVsyncId();
+ stateData.mStateDataKey = stateKey;
+ stateData.mWidgetState = widgetState;
+ stateData.mWidgetCategory = widgetCategory;
+ stateData.mWidgetId = widgetId;
+ stateData.mVsyncIdEnd = Long.MAX_VALUE;
+ mActiveStates.put(stateKey, stateData);
+
+ }
+
+ /**
+ * Will add all previously encountered states as well as all currently active states to the list
+ * that was passed in.
+ * @param allStates the list that will be populated with the widget states.
+ */
+ public void retrieveAllStates(ArrayList<StateData> allStates) {
+ synchronized (mLock) {
+ allStates.addAll(mPreviousStates);
+ allStates.addAll(mActiveStates.values());
+ }
+ }
+
+ /**
+ * Call after processing a batch of JankData, will remove any processed states from the
+ * previous state list.
+ */
+ public void stateProcessingComplete() {
+ synchronized (mLock) {
+ for (int i = mPreviousStates.size() - 1; i >= 0; i--) {
+ StateData stateData = mPreviousStates.get(i);
+ if (stateData.mProcessed) {
+ mPreviousStates.remove(stateData);
+ mStateDataObjectPool.release(stateData);
+ }
+ }
+ }
+ }
+
+ /**
+ * Only intended to be used for testing, this enables test methods to submit pending states
+ * with known start and end vsyncids. This allows testing methods to know the exact ranges
+ * of vysncid and calculate exactly how many states should or should not be processed.
+ * @param stateData the data that will be added.
+ *
+ */
+ @VisibleForTesting
+ public void addPendingStateData(List<StateData> stateData) {
+ synchronized (mLock) {
+ mPreviousStates.addAll(stateData);
+ }
+ }
+
+ private String getStateKey(String widgetCategory, String widgetId, String widgetState) {
+ return widgetCategory + widgetId + widgetState;
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class StateData {
+
+ // Concatenated string of widget category, widget state and widget id.
+ public String mStateDataKey;
+ public String mWidgetCategory;
+ public String mWidgetState;
+ public String mWidgetId;
+ // vsyncid when the state was first added.
+ public long mVsyncIdStart;
+ // vsyncid for when the state was removed.
+ public long mVsyncIdEnd;
+ // Used to indicate whether this state has been processed and can be returned to the pool.
+ public boolean mProcessed;
+ }
+}
diff --git a/tests/AppJankTest/Android.bp b/tests/AppJankTest/Android.bp
new file mode 100644
index 000000000000..acf8dc9aca47
--- /dev/null
+++ b/tests/AppJankTest/Android.bp
@@ -0,0 +1,37 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "CoreAppJankTestCases",
+ team: "trendy_team_system_performance",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.core",
+ "platform-test-annotations",
+ "flag-junit",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ certificate: "platform",
+}
diff --git a/tests/AppJankTest/AndroidManifest.xml b/tests/AppJankTest/AndroidManifest.xml
new file mode 100644
index 000000000000..ae973393b90e
--- /dev/null
+++ b/tests/AppJankTest/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.jank.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".EmptyActivity"
+ android:label="EmptyActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.app.jank.tests"
+ android:label="Core tests of App Jank Tracking">
+ </instrumentation>
+
+</manifest> \ No newline at end of file
diff --git a/tests/AppJankTest/AndroidTest.xml b/tests/AppJankTest/AndroidTest.xml
new file mode 100644
index 000000000000..c01c75c9695c
--- /dev/null
+++ b/tests/AppJankTest/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for Core App Jank Tests">
+ <option name="test-suite-tag" value="apct"/>
+
+ <option name="config-descriptor:metadata" key="component" value="systems"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+ <option name="not-shardable" value="true" />
+ <option name="install-arg" value="-t" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CoreAppJankTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.app.jank.tests"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration> \ No newline at end of file
diff --git a/tests/AppJankTest/OWNERS b/tests/AppJankTest/OWNERS
new file mode 100644
index 000000000000..806de574b071
--- /dev/null
+++ b/tests/AppJankTest/OWNERS
@@ -0,0 +1,4 @@
+steventerrell@google.com
+carmenjackson@google.com
+jjaggi@google.com
+pmuetschard@google.com \ No newline at end of file
diff --git a/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java b/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java
new file mode 100644
index 000000000000..b326765ab097
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 android.app.Activity;
+
+public class EmptyActivity extends Activity {
+}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java
new file mode 100644
index 000000000000..541009e05e55
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import android.app.jank.Flags;
+import android.app.jank.StateTracker;
+import android.app.jank.StateTracker.StateData;
+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 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;
+
+
+@RunWith(AndroidJUnit4.class)
+public class StateTrackerTest {
+
+ private static final String WIDGET_CATEGORY_NONE = "None";
+ private static final String WIDGET_CATEGORY_SCROLL = "Scroll";
+ private static final String WIDGET_STATE_IDLE = "Idle";
+ private static final String WIDGET_STATE_SCROLLING = "Scrolling";
+ private StateTracker mStateTracker;
+ private Choreographer mChoreographer;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Start an empty activity so choreographer won't return -1 for vsyncid.
+ */
+ private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityRule.close();
+ }
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mStateTracker = new StateTracker(mChoreographer);
+ }
+
+ /**
+ * Check that the start vsyncid is added when the state is first added and end vsyncid is
+ * set to the default value, indicating it has not been updated.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void addWidgetState_VerifyStateHasStartVsyncId() {
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addWidgetState_VerifyStateHasStartVsyncId");
+
+ ArrayList<StateData> stateList = new ArrayList<StateData>();
+ mStateTracker.retrieveAllStates(stateList);
+ StateData stateData = stateList.get(0);
+
+ assertTrue(stateData.mVsyncIdStart > 0);
+ assertTrue(stateData.mVsyncIdEnd == Long.MAX_VALUE);
+ }
+
+ /**
+ * Check that the end vsyncid is added when the state is removed.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void removeWidgetState_VerifyStateHasEndVsyncId() {
+
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "removeWidgetState_VerifyStateHasEndVsyncId");
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "removeWidgetState_VerifyStateHasEndVsyncId");
+
+ ArrayList<StateData> stateList = new ArrayList<StateData>();
+ mStateTracker.retrieveAllStates(stateList);
+ StateData stateData = stateList.get(0);
+
+ assertTrue(stateData.mVsyncIdStart > 0);
+ assertTrue(stateData.mVsyncIdEnd != Long.MAX_VALUE);
+ }
+
+ /**
+ * Check that duplicate states are aggregated into only one active instance.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void addDuplicateStates_ConfirmStateCountOnlyOne() {
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addDuplicateStates_ConfirmStateCountOnlyOne");
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(stateList.size(), 1);
+
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addDuplicateStates_ConfirmStateCountOnlyOne");
+
+ stateList.clear();
+
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(stateList.size(), 1);
+ }
+
+ /**
+ * Check that correct distinct states are returned when all states are retrieved.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void addThreeStateChanges_ConfirmThreeStatesReturned() {
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addThreeStateChanges_ConfirmThreeStatesReturned");
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addThreeStateChanges_ConfirmThreeStatesReturned_01");
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addThreeStateChanges_ConfirmThreeStatesReturned_02");
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(stateList.size(), 3);
+ }
+
+ /**
+ * Confirm when states are added and removed the removed states are moved to the previousStates
+ * list and returned when retrieveAllStates is called.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void simulateAddingSeveralStates() {
+ for (int i = 0; i < 20; i++) {
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i - 1));
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i));
+ }
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ int countStatesWithEndVsync = 0;
+ for (int i = 0; i < stateList.size(); i++) {
+ if (stateList.get(i).mVsyncIdEnd != Long.MAX_VALUE) {
+ countStatesWithEndVsync++;
+ }
+ }
+
+ // The last state that was added would be an active state and should not have an associated
+ // end vsyncid.
+ assertEquals(19, countStatesWithEndVsync);
+ }
+
+ /**
+ * Confirm once a state has been attributed to a frame it has been removed from the previous
+ * state list.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void confirmProcessedStates_RemovedFromPreviousStateList() {
+ for (int i = 0; i < 20; i++) {
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i - 1));
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i));
+
+ if (i == 19) {
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i));
+ }
+ }
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(20, stateList.size());
+
+ // Simulate processing all the states.
+ for (int i = 0; i < stateList.size(); i++) {
+ stateList.get(i).mProcessed = true;
+ }
+ // Clear out all processed states.
+ mStateTracker.stateProcessingComplete();
+
+ stateList.clear();
+
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(0, stateList.size());
+ }
+}