blob: 0c3b9e537b1cf91698387a720ccd300fb616b23e [file] [log] [blame]
Riddle Hsu5ef56dd62019-07-26 21:28:51 -06001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.wm;
18
19import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR;
20import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION;
21import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN;
22
23import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
24
25import static org.hamcrest.core.AnyOf.anyOf;
26import static org.hamcrest.core.Is.is;
27
28import android.app.Activity;
Tracy Zhou8089ffa2019-07-30 17:30:43 -070029import android.app.ActivityManager.TaskSnapshot;
Riddle Hsu5ef56dd62019-07-26 21:28:51 -060030import android.app.ActivityTaskManager;
31import android.app.IActivityTaskManager;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.Intent;
35import android.content.pm.PackageManager;
36import android.graphics.Rect;
37import android.os.RemoteException;
38import android.os.SystemClock;
39import android.perftests.utils.ManualBenchmarkState;
40import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
41import android.perftests.utils.PerfManualStatusReporter;
Riddle Hsu1be1dd62019-09-24 17:54:38 -060042import android.perftests.utils.PerfTestActivity;
Riddle Hsu5ef56dd62019-07-26 21:28:51 -060043import android.util.Pair;
44import android.view.IRecentsAnimationController;
45import android.view.IRecentsAnimationRunner;
46import android.view.RemoteAnimationTarget;
47import android.view.WindowManager;
48
49import androidx.test.filters.LargeTest;
50import androidx.test.rule.ActivityTestRule;
51import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
52import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
53import androidx.test.runner.lifecycle.Stage;
54
55import org.junit.After;
56import org.junit.AfterClass;
57import org.junit.Assume;
58import org.junit.Before;
59import org.junit.BeforeClass;
60import org.junit.Rule;
61import org.junit.Test;
62import org.junit.runner.RunWith;
63import org.junit.runners.Parameterized;
64
65import java.util.ArrayList;
66import java.util.Arrays;
67import java.util.Collection;
68import java.util.concurrent.Semaphore;
69import java.util.concurrent.TimeUnit;
70
71@RunWith(Parameterized.class)
72@LargeTest
73public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
74 private static Intent sRecentsIntent;
75
76 @Rule
77 public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
78
79 @Rule
Riddle Hsu1be1dd62019-09-24 17:54:38 -060080 public final ActivityTestRule<PerfTestActivity> mActivityRule = new ActivityTestRule<>(
81 PerfTestActivity.class, false /* initialTouchMode */, false /* launchActivity */);
Riddle Hsu5ef56dd62019-07-26 21:28:51 -060082
83 private long mMeasuredTimeNs;
84 private LifecycleListener mLifecycleListener;
85
86 @Parameterized.Parameter(0)
87 public int intervalBetweenOperations;
88
89 @Parameterized.Parameters(name = "interval{0}ms")
90 public static Collection<Object[]> getParameters() {
91 return Arrays.asList(new Object[][] {
92 { 0 },
93 { 100 },
94 { 300 },
95 });
96 }
97
98 @BeforeClass
99 public static void setUpClass() {
100 // Get the permission to invoke startRecentsActivity.
101 sUiAutomation.adoptShellPermissionIdentity();
102
103 final Context context = getInstrumentation().getContext();
104 final PackageManager pm = context.getPackageManager();
105 final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>());
106
107 try {
108 final ComponentName recentsComponent =
109 ComponentName.unflattenFromString(context.getResources().getString(
110 com.android.internal.R.string.config_recentsComponentName));
111 final int enabledState = pm.getComponentEnabledSetting(recentsComponent);
112 Assume.assumeThat(enabledState, anyOf(
113 is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT),
114 is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)));
115
116 final boolean homeIsRecents =
117 recentsComponent.getPackageName().equals(defaultHome.getPackageName());
118 sRecentsIntent =
119 new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent);
120 } catch (Exception e) {
121 Assume.assumeNoException(e);
122 }
123 }
124
125 @AfterClass
126 public static void tearDownClass() {
127 sUiAutomation.dropShellPermissionIdentity();
128 }
129
130 @Before
131 @Override
132 public void setUp() {
133 super.setUp();
134 final Activity testActivity = mActivityRule.launchActivity(null /* intent */);
135 try {
136 mActivityRule.runOnUiThread(() -> testActivity.getWindow()
137 .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
138 } catch (Throwable ignored) { }
139 mLifecycleListener = new LifecycleListener(testActivity);
140 ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener);
141 }
142
143 @After
144 public void tearDown() {
145 ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener);
146 }
147
148 /** Simulate the timing of touch. */
149 private void makeInterval() {
150 SystemClock.sleep(intervalBetweenOperations);
151 }
152
153 /**
154 * <pre>
155 * Steps:
156 * (1) Start recents activity (only make it visible).
157 * (2) Finish animation, take turns to execute (a), (b).
158 * (a) Move recents activity to top.
159 * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP})
160 * Move test app to top by startActivityFromRecents.
161 * (b) Cancel (it is similar to swipe a little distance and give up to enter recents).
162 * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION})
163 * (3) Loop (1).
164 * </pre>
165 */
166 @Test
167 @ManualBenchmarkTest(
168 warmupDurationNs = TIME_1_S_IN_NS,
169 targetTestDurationNs = TIME_5_S_IN_NS,
170 statsReportFlags =
171 STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR)
172 public void testRecentsAnimation() throws Throwable {
173 final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
174 final IActivityTaskManager atm = ActivityTaskManager.getService();
175
176 final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>();
177 // Real launch the recents activity.
178 finishCases.add(new Pair<>("finishMoveToTop", true));
179 // Return to the original top.
180 finishCases.add(new Pair<>("finishCancel", false));
181
182 // Ensure startRecentsActivity won't be called before finishing the animation.
183 final Semaphore recentsSemaphore = new Semaphore(1);
184
185 final int testActivityTaskId = mActivityRule.getActivity().getTaskId();
186 final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() {
187 int mIteration;
188
189 @Override
190 public void onAnimationStart(IRecentsAnimationController controller,
Winson Chungd5852192019-09-06 17:20:28 -0700191 RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
192 Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException {
Riddle Hsu5ef56dd62019-07-26 21:28:51 -0600193 final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
194 final boolean moveRecentsToTop = finishCase.second;
195 makeInterval();
196
197 long startTime = SystemClock.elapsedRealtimeNanos();
198 controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */);
199 final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime;
200 mMeasuredTimeNs += elapsedTimeNsOfFinish;
201 state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
202
203 if (moveRecentsToTop) {
204 mLifecycleListener.waitForIdleSync(Stage.STOPPED);
205
206 startTime = SystemClock.elapsedRealtimeNanos();
207 atm.startActivityFromRecents(testActivityTaskId, null /* options */);
208 final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
209 mMeasuredTimeNs += elapsedTimeNs;
210 state.addExtraResult("startFromRecents", elapsedTimeNs);
211
212 mLifecycleListener.waitForIdleSync(Stage.RESUMED);
213 }
214
215 makeInterval();
216 recentsSemaphore.release();
217 }
218
219 @Override
Tracy Zhou8089ffa2019-07-30 17:30:43 -0700220 public void onAnimationCanceled(TaskSnapshot taskSnapshot) throws RemoteException {
Riddle Hsu5ef56dd62019-07-26 21:28:51 -0600221 Assume.assumeNoException(
222 new AssertionError("onAnimationCanceled should not be called"));
223 }
224 };
225
226 while (state.keepRunning(mMeasuredTimeNs)) {
227 Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
228
229 final long startTime = SystemClock.elapsedRealtimeNanos();
230 atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
231 final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
232 mMeasuredTimeNs += elapsedTimeNsOfStart;
233 state.addExtraResult("start", elapsedTimeNsOfStart);
234 }
235
236 // Ensure the last round of animation callback is done.
237 recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS);
238 recentsSemaphore.release();
239 }
240
241 private static class LifecycleListener implements ActivityLifecycleCallback {
242 private final Activity mTargetActivity;
243 private Stage mWaitingStage;
244 private Stage mReceivedStage;
245
246 LifecycleListener(Activity activity) {
247 mTargetActivity = activity;
248 }
249
250 void waitForIdleSync(Stage state) {
251 synchronized (this) {
252 if (state != mReceivedStage) {
253 mWaitingStage = state;
254 try {
255 wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
256 } catch (InterruptedException impossible) { }
257 }
258 mWaitingStage = mReceivedStage = null;
259 }
260 getInstrumentation().waitForIdleSync();
261 }
262
263 @Override
264 public void onActivityLifecycleChanged(Activity activity, Stage stage) {
265 if (mTargetActivity != activity) {
266 return;
267 }
268
269 synchronized (this) {
270 mReceivedStage = stage;
271 if (mWaitingStage == mReceivedStage) {
272 notifyAll();
273 }
274 }
275 }
276 }
277}