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