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.StatsReport; 20 21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 22 23 import static org.hamcrest.core.AnyOf.anyOf; 24 import static org.hamcrest.core.Is.is; 25 26 import android.app.ActivityManager; 27 import android.app.ActivityManager.TaskSnapshot; 28 import android.app.ActivityTaskManager; 29 import android.app.IActivityTaskManager; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.pm.PackageManager; 34 import android.graphics.Rect; 35 import android.os.RemoteException; 36 import android.os.SystemClock; 37 import android.perftests.utils.ManualBenchmarkState; 38 import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest; 39 import android.perftests.utils.PerfManualStatusReporter; 40 import android.util.Pair; 41 import android.view.IRecentsAnimationController; 42 import android.view.IRecentsAnimationRunner; 43 import android.view.RemoteAnimationTarget; 44 45 import androidx.test.filters.LargeTest; 46 import androidx.test.runner.lifecycle.Stage; 47 48 import org.junit.AfterClass; 49 import org.junit.Assume; 50 import org.junit.Before; 51 import org.junit.BeforeClass; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.junit.runners.Parameterized; 56 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.concurrent.Semaphore; 61 import java.util.concurrent.TimeUnit; 62 63 @RunWith(Parameterized.class) 64 @LargeTest 65 public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { 66 private static Intent sRecentsIntent; 67 68 @Rule 69 public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter(); 70 71 @Rule 72 public final PerfTestActivityRule mActivityRule = 73 new PerfTestActivityRule(true /* launchActivity */); 74 75 private long mMeasuredTimeNs; 76 77 /** 78 * Used to skip each test method if there is error. It cannot be raised in static setup because 79 * that will break the amount of target method count. 80 */ 81 private static Exception sSetUpClassException; 82 83 @Parameterized.Parameter(0) 84 public int intervalBetweenOperations; 85 86 @Parameterized.Parameters(name = "interval{0}ms") getParameters()87 public static Collection<Object[]> getParameters() { 88 return Arrays.asList(new Object[][] { 89 { 0 }, 90 { 100 }, 91 { 300 }, 92 }); 93 } 94 95 @BeforeClass setUpClass()96 public static void setUpClass() { 97 // Get the permission to invoke startRecentsActivity. 98 sUiAutomation.adoptShellPermissionIdentity(); 99 100 final Context context = getInstrumentation().getContext(); 101 final PackageManager pm = context.getPackageManager(); 102 final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>()); 103 104 try { 105 final ComponentName recentsComponent = 106 ComponentName.unflattenFromString(context.getResources().getString( 107 com.android.internal.R.string.config_recentsComponentName)); 108 final int enabledState = pm.getComponentEnabledSetting(recentsComponent); 109 Assume.assumeThat(enabledState, anyOf( 110 is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT), 111 is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED))); 112 113 final boolean homeIsRecents = 114 recentsComponent.getPackageName().equals(defaultHome.getPackageName()); 115 sRecentsIntent = 116 new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent); 117 } catch (Exception e) { 118 sSetUpClassException = e; 119 } 120 } 121 122 @AfterClass tearDownClass()123 public static void tearDownClass() { 124 sSetUpClassException = null; 125 try { 126 // Recents activity may stop app switches. Restore the state to avoid affecting 127 // the next test. 128 ActivityManager.resumeAppSwitches(); 129 } catch (RemoteException ignored) { 130 } 131 sUiAutomation.dropShellPermissionIdentity(); 132 } 133 134 @Before setUp()135 public void setUp() { 136 Assume.assumeNoException(sSetUpClassException); 137 } 138 139 /** Simulate the timing of touch. */ makeInterval()140 private void makeInterval() { 141 SystemClock.sleep(intervalBetweenOperations); 142 } 143 144 /** 145 * <pre> 146 * Steps: 147 * (1) Start recents activity (only make it visible). 148 * (2) Finish animation, take turns to execute (a), (b). 149 * (a) Move recents activity to top. 150 * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP}) 151 * Move test app to top by startActivityFromRecents. 152 * (b) Cancel (it is similar to swipe a little distance and give up to enter recents). 153 * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION}) 154 * (3) Loop (1). 155 * </pre> 156 */ 157 @Test 158 @ManualBenchmarkTest( 159 warmupDurationNs = TIME_1_S_IN_NS, 160 targetTestDurationNs = TIME_5_S_IN_NS, 161 statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN 162 | StatsReport.FLAG_COEFFICIENT_VAR)) testRecentsAnimation()163 public void testRecentsAnimation() throws Throwable { 164 final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 165 final IActivityTaskManager atm = ActivityTaskManager.getService(); 166 167 final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>(); 168 // Real launch the recents activity. 169 finishCases.add(new Pair<>("finishMoveToTop", true)); 170 // Return to the original top. 171 finishCases.add(new Pair<>("finishCancel", false)); 172 173 // Ensure startRecentsActivity won't be called before finishing the animation. 174 final Semaphore recentsSemaphore = new Semaphore(1); 175 176 final int testActivityTaskId = mActivityRule.getActivity().getTaskId(); 177 final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() { 178 int mIteration; 179 180 @Override 181 public void onAnimationStart(IRecentsAnimationController controller, 182 RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, 183 Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException { 184 final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2); 185 final boolean moveRecentsToTop = finishCase.second; 186 makeInterval(); 187 188 long startTime = SystemClock.elapsedRealtimeNanos(); 189 controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */); 190 final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime; 191 mMeasuredTimeNs += elapsedTimeNsOfFinish; 192 state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish); 193 194 if (moveRecentsToTop) { 195 mActivityRule.waitForIdleSync(Stage.STOPPED); 196 197 startTime = SystemClock.elapsedRealtimeNanos(); 198 atm.startActivityFromRecents(testActivityTaskId, null /* options */); 199 final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime; 200 mMeasuredTimeNs += elapsedTimeNs; 201 state.addExtraResult("startFromRecents", elapsedTimeNs); 202 203 mActivityRule.waitForIdleSync(Stage.RESUMED); 204 } 205 206 makeInterval(); 207 recentsSemaphore.release(); 208 } 209 210 @Override 211 public void onAnimationCanceled(TaskSnapshot taskSnapshot) throws RemoteException { 212 Assume.assumeNoException( 213 new AssertionError("onAnimationCanceled should not be called")); 214 } 215 216 @Override 217 public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { 218 /* no-op */ 219 } 220 }; 221 222 recentsSemaphore.tryAcquire(); 223 while (state.keepRunning(mMeasuredTimeNs)) { 224 mMeasuredTimeNs = 0; 225 226 final long startTime = SystemClock.elapsedRealtimeNanos(); 227 atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim); 228 final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime; 229 mMeasuredTimeNs += elapsedTimeNsOfStart; 230 state.addExtraResult("start", elapsedTimeNsOfStart); 231 232 // Ensure the animation callback is done. 233 Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS)); 234 } 235 } 236 } 237