Riddle Hsu | 5ef56dd6 | 2019-07-26 21:28:51 -0600 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package android.wm; |
| 18 | |
| 19 | import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR; |
| 20 | import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION; |
| 21 | import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN; |
| 22 | |
| 23 | import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| 24 | |
| 25 | import static org.hamcrest.core.AnyOf.anyOf; |
| 26 | import static org.hamcrest.core.Is.is; |
| 27 | |
| 28 | import android.app.Activity; |
Tracy Zhou | 8089ffa | 2019-07-30 17:30:43 -0700 | [diff] [blame] | 29 | import android.app.ActivityManager.TaskSnapshot; |
Riddle Hsu | 5ef56dd6 | 2019-07-26 21:28:51 -0600 | [diff] [blame] | 30 | import android.app.ActivityTaskManager; |
| 31 | import android.app.IActivityTaskManager; |
| 32 | import android.content.ComponentName; |
| 33 | import android.content.Context; |
| 34 | import android.content.Intent; |
| 35 | import android.content.pm.PackageManager; |
| 36 | import android.graphics.Rect; |
| 37 | import android.os.RemoteException; |
| 38 | import android.os.SystemClock; |
| 39 | import android.perftests.utils.ManualBenchmarkState; |
| 40 | import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest; |
| 41 | import android.perftests.utils.PerfManualStatusReporter; |
Riddle Hsu | 1be1dd6 | 2019-09-24 17:54:38 -0600 | [diff] [blame^] | 42 | import android.perftests.utils.PerfTestActivity; |
Riddle Hsu | 5ef56dd6 | 2019-07-26 21:28:51 -0600 | [diff] [blame] | 43 | import android.util.Pair; |
| 44 | import android.view.IRecentsAnimationController; |
| 45 | import android.view.IRecentsAnimationRunner; |
| 46 | import android.view.RemoteAnimationTarget; |
| 47 | import android.view.WindowManager; |
| 48 | |
| 49 | import androidx.test.filters.LargeTest; |
| 50 | import androidx.test.rule.ActivityTestRule; |
| 51 | import androidx.test.runner.lifecycle.ActivityLifecycleCallback; |
| 52 | import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; |
| 53 | import androidx.test.runner.lifecycle.Stage; |
| 54 | |
| 55 | import org.junit.After; |
| 56 | import org.junit.AfterClass; |
| 57 | import org.junit.Assume; |
| 58 | import org.junit.Before; |
| 59 | import org.junit.BeforeClass; |
| 60 | import org.junit.Rule; |
| 61 | import org.junit.Test; |
| 62 | import org.junit.runner.RunWith; |
| 63 | import org.junit.runners.Parameterized; |
| 64 | |
| 65 | import java.util.ArrayList; |
| 66 | import java.util.Arrays; |
| 67 | import java.util.Collection; |
| 68 | import java.util.concurrent.Semaphore; |
| 69 | import java.util.concurrent.TimeUnit; |
| 70 | |
| 71 | @RunWith(Parameterized.class) |
| 72 | @LargeTest |
| 73 | public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { |
| 74 | private static Intent sRecentsIntent; |
| 75 | |
| 76 | @Rule |
| 77 | public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter(); |
| 78 | |
| 79 | @Rule |
Riddle Hsu | 1be1dd6 | 2019-09-24 17:54:38 -0600 | [diff] [blame^] | 80 | public final ActivityTestRule<PerfTestActivity> mActivityRule = new ActivityTestRule<>( |
| 81 | PerfTestActivity.class, false /* initialTouchMode */, false /* launchActivity */); |
Riddle Hsu | 5ef56dd6 | 2019-07-26 21:28:51 -0600 | [diff] [blame] | 82 | |
| 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 Chung | d585219 | 2019-09-06 17:20:28 -0700 | [diff] [blame] | 191 | RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, |
| 192 | Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException { |
Riddle Hsu | 5ef56dd6 | 2019-07-26 21:28:51 -0600 | [diff] [blame] | 193 | 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 Zhou | 8089ffa | 2019-07-30 17:30:43 -0700 | [diff] [blame] | 220 | public void onAnimationCanceled(TaskSnapshot taskSnapshot) throws RemoteException { |
Riddle Hsu | 5ef56dd6 | 2019-07-26 21:28:51 -0600 | [diff] [blame] | 221 | 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 | } |