1 /* 2 * Copyright (C) 2018 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.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 24 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 25 import static android.server.wm.WindowManagerState.STATE_INITIALIZING; 26 import static android.server.wm.WindowManagerState.STATE_STOPPED; 27 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY; 28 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; 29 import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY; 30 import static android.server.wm.app.Components.TEST_ACTIVITY; 31 import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY; 32 import static android.server.wm.app.Components.TestActivity.COMMAND_NAVIGATE_UP_TO; 33 import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES; 34 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT; 35 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS; 36 import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY; 37 import static android.server.wm.second.Components.SECOND_ACTIVITY; 38 import static android.view.Display.DEFAULT_DISPLAY; 39 40 import static com.google.common.truth.Truth.assertWithMessage; 41 42 import static org.junit.Assert.assertEquals; 43 import static org.junit.Assert.assertFalse; 44 import static org.junit.Assert.assertNotEquals; 45 46 import android.app.Activity; 47 import android.app.ActivityOptions; 48 import android.content.ComponentName; 49 import android.content.Intent; 50 import android.os.Bundle; 51 import android.platform.test.annotations.Presubmit; 52 import android.server.wm.CommandSession.ActivitySession; 53 import android.server.wm.intent.Activities; 54 55 import org.junit.Test; 56 57 import java.util.Arrays; 58 import java.util.List; 59 import java.util.stream.Collectors; 60 import java.util.stream.Stream; 61 62 /** 63 * Build/Install/Run: 64 * atest CtsWindowManagerDeviceTestCases:StartActivityTests 65 */ 66 @Presubmit 67 public class StartActivityTests extends ActivityManagerTestBase { 68 69 @Test testStartHomeIfNoActivities()70 public void testStartHomeIfNoActivities() { 71 if (!hasHomeScreen()) { 72 return; 73 } 74 75 final ComponentName defaultHome = getDefaultHomeComponent(); 76 final int[] allActivityTypes = Arrays.copyOf(ALL_ACTIVITY_TYPE_BUT_HOME, 77 ALL_ACTIVITY_TYPE_BUT_HOME.length + 1); 78 allActivityTypes[allActivityTypes.length - 1] = ACTIVITY_TYPE_HOME; 79 removeRootTasksWithActivityTypes(allActivityTypes); 80 81 waitAndAssertResumedActivity(defaultHome, 82 "Home activity should be restarted after force-finish"); 83 84 stopTestPackage(defaultHome.getPackageName()); 85 86 waitAndAssertResumedActivity(defaultHome, 87 "Home activity should be restarted after force-stop"); 88 } 89 90 /** 91 * Ensures {@link Activity} without {@link Intent#FLAG_ACTIVITY_NEW_TASK} can only be launched 92 * from an {@link Activity} {@link android.content.Context}. 93 */ 94 @Test testStartActivityContexts()95 public void testStartActivityContexts() { 96 // Note by default LaunchActivityBuilder will use LAUNCHING_ACTIVITY to launch the target. 97 98 // Launch Activity from application context without FLAG_ACTIVITY_NEW_TASK. 99 getLaunchActivityBuilder() 100 .setTargetActivity(TEST_ACTIVITY) 101 .setUseApplicationContext(true) 102 .setSuppressExceptions(true) 103 .setWaitForLaunched(false) 104 .execute(); 105 106 // Launch another activity from activity to ensure previous one has done. 107 getLaunchActivityBuilder() 108 .setTargetActivity(NO_RELAUNCH_ACTIVITY) 109 .execute(); 110 111 mWmState.computeState(NO_RELAUNCH_ACTIVITY); 112 113 // Verify Activity was not started. 114 assertFalse(mWmState.containsActivity(TEST_ACTIVITY)); 115 mWmState.assertResumedActivity( 116 "Activity launched from activity context should be present", NO_RELAUNCH_ACTIVITY); 117 } 118 119 /** 120 * Ensures you can start an {@link Activity} from a non {@link Activity} 121 * {@link android.content.Context} with the {@code FLAG_ACTIVITY_NEW_TASK}. 122 */ 123 @Test testStartActivityNewTask()124 public void testStartActivityNewTask() throws Exception { 125 // Launch Activity from application context. 126 getLaunchActivityBuilder() 127 .setTargetActivity(TEST_ACTIVITY) 128 .setUseApplicationContext(true) 129 .setSuppressExceptions(true) 130 .setNewTask(true) 131 .execute(); 132 133 mWmState.computeState(TEST_ACTIVITY); 134 mWmState.assertResumedActivity("Test Activity should be started with new task flag", 135 TEST_ACTIVITY); 136 } 137 138 @Test testStartActivityTaskLaunchBehind()139 public void testStartActivityTaskLaunchBehind() { 140 // launch an activity 141 getLaunchActivityBuilder() 142 .setTargetActivity(TEST_ACTIVITY) 143 .setUseInstrumentation() 144 .setNewTask(true) 145 .execute(); 146 147 // launch an activity behind 148 getLaunchActivityBuilder() 149 .setTargetActivity(TRANSLUCENT_ACTIVITY) 150 .setUseInstrumentation() 151 .setIntentFlags(FLAG_ACTIVITY_NEW_DOCUMENT) 152 .setNewTask(true) 153 .setLaunchTaskBehind(true) 154 .execute(); 155 156 waitAndAssertActivityState(TRANSLUCENT_ACTIVITY, STATE_STOPPED, 157 "Activity should be stopped"); 158 mWmState.assertResumedActivity("Test Activity should be remained on top and resumed", 159 TEST_ACTIVITY); 160 } 161 162 @Test testStartActivityFromFinishingActivity()163 public void testStartActivityFromFinishingActivity() { 164 // launch TEST_ACTIVITY from LAUNCHING_ACTIVITY 165 getLaunchActivityBuilder() 166 .setTargetActivity(TEST_ACTIVITY) 167 .setFinishBeforeLaunch(true) 168 .execute(); 169 170 // launch LAUNCHING_ACTIVITY again 171 getLaunchActivityBuilder() 172 .setTargetActivity(LAUNCHING_ACTIVITY) 173 .setUseInstrumentation() 174 .execute(); 175 176 // make sure TEST_ACTIVITY is still on top and resumed 177 mWmState.computeState(TEST_ACTIVITY); 178 mWmState.assertResumedActivity("Test Activity should be remained on top and resumed", 179 TEST_ACTIVITY); 180 } 181 182 /** 183 * Ensures you can start an {@link Activity} from a non {@link Activity} 184 * {@link android.content.Context} when the target sdk is between N and O Mr1. 185 * @throws Exception 186 */ 187 @Test testLegacyStartActivityFromNonActivityContext()188 public void testLegacyStartActivityFromNonActivityContext() { 189 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY) 190 .setLaunchingActivity(SDK_27_LAUNCHING_ACTIVITY) 191 .setUseApplicationContext(true) 192 .execute(); 193 194 mWmState.computeState(TEST_ACTIVITY); 195 mWmState.assertResumedActivity("Test Activity should be resumed without older sdk", 196 TEST_ACTIVITY); 197 } 198 199 /** 200 * Starts 3 activities A, B, C in the same task. A and B belong to current package and are not 201 * exported. C belongs to a different package with different uid. After C called 202 * {@link Activity#navigateUpTo(Intent)} with the intent of A, the activities B, C should be 203 * finished and instead of creating a new instance of A, the original A should become the top 204 * activity because the caller C in different uid cannot launch a non-exported activity. 205 */ 206 @Test testStartActivityByNavigateUpToFromDiffUid()207 public void testStartActivityByNavigateUpToFromDiffUid() { 208 final Intent intent1 = new Intent(mContext, Activities.RegularActivity.class); 209 final String regularActivityName = Activities.RegularActivity.class.getName(); 210 final TestActivitySession<Activities.RegularActivity> activitySession1 = 211 createManagedTestActivitySession(); 212 activitySession1.launchTestActivityOnDisplaySync(regularActivityName, intent1, 213 DEFAULT_DISPLAY); 214 final TestActivitySession<Activities.SingleTopActivity> activitySession2 = 215 createManagedTestActivitySession(); 216 activitySession2.launchTestActivityOnDisplaySync(Activities.SingleTopActivity.class, 217 DEFAULT_DISPLAY); 218 219 final CommandSession.ActivitySession activitySession3 = 220 createManagedActivityClientSession().startActivity( 221 new CommandSession.DefaultLaunchProxy() { 222 @Override 223 public void execute() { 224 final Intent intent = new Intent().setComponent(TEST_ACTIVITY); 225 mLaunchInjector.setupIntent(intent); 226 activitySession2.getActivity().startActivity(intent); 227 } 228 }); 229 230 final Bundle data = new Bundle(); 231 data.putParcelable(EXTRA_INTENT, intent1); 232 activitySession3.sendCommand(COMMAND_NAVIGATE_UP_TO, data); 233 234 waitAndAssertTopResumedActivity(intent1.getComponent(), DEFAULT_DISPLAY, 235 "navigateUpTo should return to the first activity"); 236 // Make sure the resumed first activity is the original instance. 237 assertFalse("The target of navigateUpTo should not be destroyed", 238 activitySession1.getActivity().isDestroyed()); 239 240 // The activities above the first one should be destroyed. 241 mWmState.waitAndAssertActivityRemoved( 242 activitySession3.getOriginalLaunchIntent().getComponent()); 243 mWmState.waitAndAssertActivityRemoved(activitySession2.getActivity().getComponentName()); 244 } 245 246 /** 247 * Assume there are 3 activities (A1, A2, A3) with different task affinities and the same uid. 248 * After A1 called {@link Activity#startActivities} to start A2 (with NEW_TASK) and A3, the 249 * result should be 2 tasks: [A1] and [A2, A3]. 250 */ 251 @Test testStartActivitiesInNewAndSameTask()252 public void testStartActivitiesInNewAndSameTask() { 253 final int[] taskIds = startActivitiesAndGetTaskIds(new Intent[] { 254 new Intent().setComponent(NO_RELAUNCH_ACTIVITY) 255 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 256 new Intent().setComponent(LAUNCHING_ACTIVITY) }); 257 258 assertNotEquals("The activity with different task affinity started by flag NEW_TASK" 259 + " should be in a different task", taskIds[0], taskIds[1]); 260 assertEquals("The activity started without flag NEW_TASK should be put in the same task", 261 taskIds[1], taskIds[2]); 262 } 263 264 @Test testNormalActivityCanNotSetActivityType()265 public void testNormalActivityCanNotSetActivityType() { 266 // Activities should not be started if the launch activity type is set. 267 boolean useShellPermission = false; 268 startingActivityWithType(ACTIVITY_TYPE_STANDARD, useShellPermission); 269 startingActivityWithType(ACTIVITY_TYPE_HOME, useShellPermission); 270 startingActivityWithType(ACTIVITY_TYPE_RECENTS, useShellPermission); 271 startingActivityWithType(ACTIVITY_TYPE_ASSISTANT, useShellPermission); 272 startingActivityWithType(ACTIVITY_TYPE_DREAM, useShellPermission); 273 274 // Activities can be started because they are started with shell permissions. 275 useShellPermission = true; 276 startingActivityWithType(ACTIVITY_TYPE_STANDARD, useShellPermission); 277 startingActivityWithType(ACTIVITY_TYPE_HOME, useShellPermission); 278 startingActivityWithType(ACTIVITY_TYPE_RECENTS, useShellPermission); 279 startingActivityWithType(ACTIVITY_TYPE_ASSISTANT, useShellPermission); 280 startingActivityWithType(ACTIVITY_TYPE_DREAM, useShellPermission); 281 } 282 startingActivityWithType(int type, boolean useShellPermission)283 private void startingActivityWithType(int type, boolean useShellPermission) { 284 separateTestJournal(); 285 getLaunchActivityBuilder() 286 .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY) 287 .setUseInstrumentation() 288 .setWithShellPermission(useShellPermission) 289 .setActivityType(type) 290 .setWaitForLaunched(false) 291 .setMultipleTask(true) 292 .execute(); 293 294 mWmState.computeState(); 295 if (useShellPermission) { 296 waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY, 297 "Activity should be started and resumed"); 298 mWmState.assertFrontStackActivityType("The activity type should be same as requested.", 299 type); 300 mBroadcastActionTrigger.finishBroadcastReceiverActivity(); 301 mWmState.waitAndAssertActivityRemoved(BROADCAST_RECEIVER_ACTIVITY); 302 } else { 303 assertSecurityExceptionFromActivityLauncher(); 304 } 305 } 306 307 /** 308 * Assume there are 3 activities (A1, A2, B1) with default launch mode. The uid of B1 is 309 * different from A1 and A2. After A1 called {@link Activity#startActivities} to start B1 and 310 * A2, the result should be 3 tasks. 311 */ 312 @Test testStartActivitiesWithDiffUidNotInSameTask()313 public void testStartActivitiesWithDiffUidNotInSameTask() { 314 final int[] taskIds = startActivitiesAndGetTaskIds(new Intent[] { 315 new Intent().setComponent(SECOND_ACTIVITY) 316 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 317 new Intent().setComponent(LAUNCHING_ACTIVITY) }); 318 319 assertNotEquals("The activity in a different application (uid) started by flag NEW_TASK" 320 + " should be in a different task", taskIds[0], taskIds[1]); 321 assertWithMessage("The last started activity should be in a different task because " 322 + SECOND_ACTIVITY + " has a different uid from the source caller") 323 .that(taskIds[2]).isNotIn(Arrays.asList(taskIds[0], taskIds[1])); 324 } 325 326 /** 327 * Test the activity launched with ActivityOptions#setTaskOverlay should remain on top of the 328 * task after start another activity. 329 */ 330 @Test testStartActivitiesTaskOverlayStayOnTop()331 public void testStartActivitiesTaskOverlayStayOnTop() { 332 final Intent baseIntent = new Intent(mContext, Activities.RegularActivity.class); 333 final String regularActivityName = Activities.RegularActivity.class.getName(); 334 final TestActivitySession<Activities.RegularActivity> activitySession = 335 createManagedTestActivitySession(); 336 activitySession.launchTestActivityOnDisplaySync(regularActivityName, baseIntent, 337 DEFAULT_DISPLAY); 338 mWmState.computeState(baseIntent.getComponent()); 339 final int taskId = mWmState.getTaskByActivity(baseIntent.getComponent()).getTaskId(); 340 final Activity baseActivity = activitySession.getActivity(); 341 342 final ActivityOptions overlayOptions = ActivityOptions.makeBasic(); 343 overlayOptions.setTaskOverlay(true, true); 344 overlayOptions.setLaunchTaskId(taskId); 345 final Intent taskOverlay = new Intent().setComponent(SECOND_ACTIVITY); 346 runWithShellPermission(() -> 347 baseActivity.startActivity(taskOverlay, overlayOptions.toBundle())); 348 349 waitAndAssertResumedActivity(taskOverlay.getComponent(), 350 "taskOverlay activity on top"); 351 final Intent behindOverlay = new Intent().setComponent(TEST_ACTIVITY); 352 baseActivity.startActivity(behindOverlay); 353 354 waitAndAssertActivityState(TEST_ACTIVITY, STATE_INITIALIZING, 355 "Activity behind taskOverlay should not resumed"); 356 // check order: SecondActivity(top) -> TestActivity -> RegularActivity(base) 357 final List<String> activitiesOrder = mWmState.getTaskByActivity(baseIntent.getComponent()) 358 .mActivities 359 .stream() 360 .map(WindowManagerState.Activity::getName) 361 .collect(Collectors.toList()); 362 363 final List<String> expectedOrder = Stream.of( 364 SECOND_ACTIVITY, 365 TEST_ACTIVITY, 366 baseIntent.getComponent()) 367 .map(c -> c.flattenToShortString()) 368 .collect(Collectors.toList()); 369 assertEquals(activitiesOrder, expectedOrder); 370 mWmState.assertResumedActivity("TaskOverlay activity should be remained on top and " 371 + "resumed", taskOverlay.getComponent()); 372 } 373 374 /** 375 * Invokes {@link android.app.Activity#startActivities} from {@link #TEST_ACTIVITY} and returns 376 * the task id of each started activity (the index 0 will be the caller {@link #TEST_ACTIVITY}). 377 */ startActivitiesAndGetTaskIds(Intent[] intents)378 private int[] startActivitiesAndGetTaskIds(Intent[] intents) { 379 final ActivitySession activity = createManagedActivityClientSession() 380 .startActivity(getLaunchActivityBuilder().setUseInstrumentation()); 381 final Bundle intentBundle = new Bundle(); 382 intentBundle.putParcelableArray(EXTRA_INTENTS, intents); 383 // The {@link Activity#startActivities} cannot be called from the instrumentation 384 // package because the implementation (given by test runner) may be overridden. 385 activity.sendCommand(COMMAND_START_ACTIVITIES, intentBundle); 386 387 final int[] taskIds = new int[intents.length + 1]; 388 // The {@code intents} are started, wait for the last (top) activity to be ready and then 389 // verify their task ids. 390 mWmState.computeState(intents[intents.length - 1].getComponent()); 391 taskIds[0] = mWmState.getTaskByActivity(TEST_ACTIVITY).getTaskId(); 392 for (int i = 0; i < intents.length; i++) { 393 taskIds[i + 1] = mWmState.getTaskByActivity(intents[i].getComponent()).getTaskId(); 394 } 395 return taskIds; 396 } 397 } 398