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