1/* 2 * Copyright (C) 2017 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 17package com.android.server.am; 18 19import static android.app.ActivityManager.START_ABORTED; 20import static android.app.ActivityManager.START_CLASS_NOT_FOUND; 21import static android.app.ActivityManager.START_DELIVERED_TO_TOP; 22import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; 23import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED; 24import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE; 25import static android.app.ActivityManager.START_PERMISSION_DENIED; 26import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; 27import static android.app.ActivityManager.START_SUCCESS; 28import static android.app.ActivityManager.START_SWITCHES_CANCELED; 29import static android.app.ActivityManager.START_TASK_TO_FRONT; 30import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 31import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 32import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 33import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 34import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 35 36import android.app.ActivityOptions; 37import android.app.IApplicationThread; 38import android.content.ComponentName; 39import android.content.Intent; 40import android.content.pm.ActivityInfo; 41import android.content.pm.ActivityInfo.WindowLayout; 42import android.content.pm.ApplicationInfo; 43import android.content.pm.IPackageManager; 44import android.graphics.Rect; 45import android.os.IBinder; 46import android.os.RemoteException; 47import android.platform.test.annotations.Presubmit; 48import android.service.voice.IVoiceInteractionSession; 49import android.support.test.filters.SmallTest; 50import android.support.test.runner.AndroidJUnit4; 51import android.view.Gravity; 52 53import org.junit.runner.RunWith; 54import org.junit.Test; 55 56import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; 57import static com.android.server.am.ActivityManagerService.ANIMATE; 58 59import static org.junit.Assert.assertEquals; 60import static org.junit.Assert.assertTrue; 61import static org.mockito.Mockito.any; 62import static org.mockito.Mockito.anyBoolean; 63import static org.mockito.Mockito.anyInt; 64import static org.mockito.Mockito.anyObject; 65import static org.mockito.Mockito.doAnswer; 66import static org.mockito.Mockito.doNothing; 67import static org.mockito.Mockito.doReturn; 68import static org.mockito.Mockito.eq; 69import static org.mockito.Mockito.mock; 70import static org.mockito.Mockito.spy; 71import static org.mockito.Mockito.verify; 72import static org.mockito.Mockito.times; 73 74import com.android.internal.os.BatteryStatsImpl; 75import com.android.server.am.ActivityStarter.Factory; 76import com.android.server.am.LaunchParamsController.LaunchParamsModifier; 77import com.android.server.am.TaskRecord.TaskRecordFactory; 78 79import java.util.ArrayList; 80 81/** 82 * Tests for the {@link ActivityStarter} class. 83 * 84 * Build/Install/Run: 85 * atest FrameworksServicesTests:ActivityStarterTests 86 */ 87@SmallTest 88@Presubmit 89@RunWith(AndroidJUnit4.class) 90public class ActivityStarterTests extends ActivityTestsBase { 91 private ActivityManagerService mService; 92 private ActivityStarter mStarter; 93 private ActivityStartController mController; 94 95 private static final int PRECONDITION_NO_CALLER_APP = 1; 96 private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1; 97 private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2; 98 private static final int PRECONDITION_SOURCE_PRESENT = 1 << 3; 99 private static final int PRECONDITION_REQUEST_CODE = 1 << 4; 100 private static final int PRECONDITION_SOURCE_VOICE_SESSION = 1 << 5; 101 private static final int PRECONDITION_NO_VOICE_SESSION_SUPPORT = 1 << 6; 102 private static final int PRECONDITION_DIFFERENT_UID = 1 << 7; 103 private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8; 104 private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9; 105 private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10; 106 107 @Override 108 public void setUp() throws Exception { 109 super.setUp(); 110 mService = createActivityManagerService(); 111 mController = mock(ActivityStartController.class); 112 mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor, 113 mock(ActivityStartInterceptor.class)); 114 } 115 116 @Test 117 public void testUpdateLaunchBounds() throws Exception { 118 // When in a non-resizeable stack, the task bounds should be updated. 119 final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) 120 .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( 121 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) 122 .build(); 123 final Rect bounds = new Rect(10, 10, 100, 100); 124 125 mStarter.updateBounds(task, bounds); 126 assertEquals(task.getOverrideBounds(), bounds); 127 assertEquals(new Rect(), task.getStack().getOverrideBounds()); 128 129 // When in a resizeable stack, the stack bounds should be updated as well. 130 final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor) 131 .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( 132 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */)) 133 .build(); 134 assertTrue(task2.getStack() instanceof PinnedActivityStack); 135 mStarter.updateBounds(task2, bounds); 136 137 verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId), 138 eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt()); 139 140 // In the case of no animation, the stack and task bounds should be set immediately. 141 if (!ANIMATE) { 142 assertEquals(task2.getStack().getOverrideBounds(), bounds); 143 assertEquals(task2.getOverrideBounds(), bounds); 144 } else { 145 assertEquals(task2.getOverrideBounds(), new Rect()); 146 } 147 } 148 149 @Test 150 public void testStartActivityPreconditions() throws Exception { 151 verifyStartActivityPreconditions(PRECONDITION_NO_CALLER_APP, START_PERMISSION_DENIED); 152 verifyStartActivityPreconditions(PRECONDITION_NO_INTENT_COMPONENT, 153 START_INTENT_NOT_RESOLVED); 154 verifyStartActivityPreconditions(PRECONDITION_NO_ACTIVITY_INFO, START_CLASS_NOT_FOUND); 155 verifyStartActivityPreconditions(PRECONDITION_SOURCE_PRESENT | PRECONDITION_REQUEST_CODE, 156 Intent.FLAG_ACTIVITY_FORWARD_RESULT, START_FORWARD_AND_REQUEST_CONFLICT); 157 verifyStartActivityPreconditions( 158 PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT 159 | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID, 160 START_NOT_VOICE_COMPATIBLE); 161 verifyStartActivityPreconditions( 162 PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT 163 | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID 164 | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION, 165 START_NOT_VOICE_COMPATIBLE); 166 verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED); 167 verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING, 168 START_SWITCHES_CANCELED); 169 } 170 171 private static boolean containsConditions(int preconditions, int mask) { 172 return (preconditions & mask) == mask; 173 } 174 175 private void verifyStartActivityPreconditions(int preconditions, int expectedResult) { 176 verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult); 177 } 178 179 /** 180 * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller 181 * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP}) 182 * and the launch flags specified in the intent. The method constructs a call to 183 * {@link ActivityStarter#execute} based on these preconditions and ensures the result matches 184 * the expected. It is important to note that the method also checks side effects of the start, 185 * such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios. 186 * @param preconditions A bitmask representing the preconditions for the launch 187 * @param launchFlags The launch flags to be provided by the launch {@link Intent}. 188 * @param expectedResult The expected result from the launch. 189 */ 190 private void verifyStartActivityPreconditions(int preconditions, int launchFlags, 191 int expectedResult) { 192 final ActivityManagerService service = createActivityManagerService(); 193 final IPackageManager packageManager = mock(IPackageManager.class); 194 final ActivityStartController controller = mock(ActivityStartController.class); 195 196 final ActivityStarter starter = new ActivityStarter(controller, service, 197 service.mStackSupervisor, mock(ActivityStartInterceptor.class)); 198 final IApplicationThread caller = mock(IApplicationThread.class); 199 200 // If no caller app, return {@code null} {@link ProcessRecord}. 201 final ProcessRecord record = containsConditions(preconditions, PRECONDITION_NO_CALLER_APP) 202 ? null : new ProcessRecord(null, mock(BatteryStatsImpl.class), 203 mock(ApplicationInfo.class), null, 0); 204 205 doReturn(record).when(service).getRecordForAppLocked(anyObject()); 206 207 final Intent intent = new Intent(); 208 intent.setFlags(launchFlags); 209 210 final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO) 211 ? null : new ActivityInfo(); 212 213 IVoiceInteractionSession voiceSession = 214 containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION) 215 ? mock(IVoiceInteractionSession.class) : null; 216 217 // Create source token 218 final ActivityBuilder builder = new ActivityBuilder(service).setTask( 219 new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build()); 220 221 if (aInfo != null) { 222 aInfo.applicationInfo = new ApplicationInfo(); 223 aInfo.applicationInfo.packageName = 224 ActivityBuilder.getDefaultComponent().getPackageName(); 225 } 226 227 // Offset uid by one from {@link ActivityInfo} to simulate different uids. 228 if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) { 229 builder.setUid(aInfo.applicationInfo.uid + 1); 230 } 231 232 final ActivityRecord source = builder.build(); 233 234 if (!containsConditions(preconditions, PRECONDITION_NO_INTENT_COMPONENT)) { 235 intent.setComponent(source.realActivity); 236 } 237 238 if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) { 239 doReturn(false).when(service).checkAppSwitchAllowedLocked(anyInt(), anyInt(), anyInt(), 240 anyInt(), any()); 241 } 242 243 if (containsConditions(preconditions,PRECONDITION_CANNOT_START_ANY_ACTIVITY)) { 244 doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission( 245 any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), 246 anyBoolean(), anyBoolean(), any(), any(), any()); 247 } 248 249 try { 250 if (containsConditions(preconditions, 251 PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION)) { 252 doAnswer((inv) -> { 253 throw new RemoteException(); 254 }).when(packageManager).activitySupportsIntent(eq(source.realActivity), eq(intent), 255 any()); 256 } else { 257 doReturn(!containsConditions(preconditions, PRECONDITION_NO_VOICE_SESSION_SUPPORT)) 258 .when(packageManager).activitySupportsIntent(eq(source.realActivity), 259 eq(intent), any()); 260 } 261 } catch (RemoteException e) { 262 } 263 264 final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT) 265 || containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION) 266 ? source.appToken : null; 267 268 final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE) 269 ? 1 : 0; 270 271 final int result = starter.setCaller(caller) 272 .setIntent(intent) 273 .setActivityInfo(aInfo) 274 .setResultTo(resultTo) 275 .setRequestCode(requestCode) 276 .setReason("testLaunchActivityPermissionDenied") 277 .execute(); 278 279 // In some cases the expected result internally is different than the published result. We 280 // must use ActivityStarter#getExternalResult to translate. 281 assertEquals(ActivityStarter.getExternalResult(expectedResult), result); 282 283 // Ensure that {@link ActivityOptions} are aborted with unsuccessful result. 284 if (expectedResult != START_SUCCESS) { 285 final ActivityStarter optionStarter = new ActivityStarter(mController, mService, 286 mService.mStackSupervisor, mock(ActivityStartInterceptor.class)); 287 final ActivityOptions options = spy(ActivityOptions.makeBasic()); 288 289 final int optionResult = optionStarter.setCaller(caller) 290 .setIntent(intent) 291 .setActivityInfo(aInfo) 292 .setResultTo(resultTo) 293 .setRequestCode(requestCode) 294 .setReason("testLaunchActivityPermissionDenied") 295 .setActivityOptions(new SafeActivityOptions(options)) 296 .execute(); 297 verify(options, times(1)).abort(); 298 } 299 } 300 301 private ActivityStarter prepareStarter(int launchFlags) { 302 // always allow test to start activity. 303 doReturn(true).when(mService.mStackSupervisor).checkStartAnyActivityPermission( 304 any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), 305 anyBoolean(), anyBoolean(), any(), any(), any()); 306 307 // instrument the stack and task used. 308 final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( 309 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); 310 final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) 311 .setCreateStack(false) 312 .build(); 313 314 // supervisor needs a focused stack. 315 mService.mStackSupervisor.mFocusedStack = stack; 316 317 // use factory that only returns spy task. 318 final TaskRecordFactory factory = mock(TaskRecordFactory.class); 319 TaskRecord.setTaskRecordFactory(factory); 320 321 // return task when created. 322 doReturn(task).when(factory).create(any(), anyInt(), any(), any(), any(), any()); 323 324 // direct starter to use spy stack. 325 doReturn(stack).when(mService.mStackSupervisor) 326 .getLaunchStack(any(), any(), any(), anyBoolean()); 327 doReturn(stack).when(mService.mStackSupervisor) 328 .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt()); 329 330 final Intent intent = new Intent(); 331 intent.addFlags(launchFlags); 332 intent.setComponent(ActivityBuilder.getDefaultComponent()); 333 334 final ActivityInfo info = new ActivityInfo(); 335 336 info.applicationInfo = new ApplicationInfo(); 337 info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName(); 338 339 return new ActivityStarter(mController, mService, 340 mService.mStackSupervisor, mock(ActivityStartInterceptor.class)) 341 .setIntent(intent) 342 .setActivityInfo(info); 343 } 344 345 /** 346 * Ensures that values specified at launch time are passed to {@link LaunchParamsModifier} 347 * when we are laying out a new task. 348 */ 349 @Test 350 public void testCreateTaskLayout() { 351 // modifier for validating passed values. 352 final LaunchParamsModifier modifier = mock(LaunchParamsModifier.class); 353 mService.mStackSupervisor.getLaunchParamsController().registerModifier(modifier); 354 355 // add custom values to activity info to make unique. 356 final ActivityInfo info = new ActivityInfo(); 357 final Rect launchBounds = new Rect(0, 0, 20, 30); 358 359 final WindowLayout windowLayout = 360 new WindowLayout(10, .5f, 20, 1.0f, Gravity.NO_GRAVITY, 1, 1); 361 362 info.windowLayout = windowLayout; 363 info.applicationInfo = new ApplicationInfo(); 364 info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName(); 365 366 // create starter. 367 final ActivityStarter optionStarter = prepareStarter(0 /* launchFlags */); 368 369 final ActivityOptions options = ActivityOptions.makeBasic(); 370 options.setLaunchBounds(launchBounds); 371 372 // run starter. 373 optionStarter 374 .setReason("testCreateTaskLayout") 375 .setActivityInfo(info) 376 .setActivityOptions(new SafeActivityOptions(options)) 377 .execute(); 378 379 // verify that values are passed to the modifier. 380 verify(modifier, times(1)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options), 381 any(), any()); 382 } 383 384 /** 385 * This test ensures that if the intent is being delivered to a 386 */ 387 @Test 388 public void testSplitScreenDeliverToTop() { 389 final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 390 391 final ActivityRecord focusActivity = new ActivityBuilder(mService) 392 .setCreateTask(true) 393 .build(); 394 395 focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 396 397 final ActivityRecord reusableActivity = new ActivityBuilder(mService) 398 .setCreateTask(true) 399 .build(); 400 401 // Create reusable activity after entering split-screen so that it is the top secondary 402 // stack. 403 reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); 404 405 // Set focus back to primary. 406 mService.mStackSupervisor.setFocusStackUnchecked("testSplitScreenDeliverToTop", 407 focusActivity.getStack()); 408 409 doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt()); 410 411 final int result = starter.setReason("testSplitScreenDeliverToTop").execute(); 412 413 // Ensure result is delivering intent to top. 414 assertEquals(result, START_DELIVERED_TO_TOP); 415 } 416 417 /** 418 * This test ensures that if the intent is being delivered to a split-screen unfocused task 419 * reports it is brought to front instead of delivering to top. 420 */ 421 @Test 422 public void testSplitScreenTaskToFront() { 423 final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 424 425 // Create reusable activity here first. Setting the windowing mode of the primary stack 426 // will move the existing standard full screen stack to secondary, putting this one on the 427 // bottom. 428 final ActivityRecord reusableActivity = new ActivityBuilder(mService) 429 .setCreateTask(true) 430 .build(); 431 432 reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); 433 434 final ActivityRecord focusActivity = new ActivityBuilder(mService) 435 .setCreateTask(true) 436 .build(); 437 438 // Enter split-screen. Primary stack should have focus. 439 focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 440 441 doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt()); 442 443 final int result = starter.setReason("testSplitScreenMoveToFront").execute(); 444 445 // Ensure result is moving task to front. 446 assertEquals(result, START_TASK_TO_FRONT); 447 } 448 449 /** 450 * Tests activity is cleaned up properly in a task mode violation. 451 */ 452 @Test 453 public void testTaskModeViolation() { 454 final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); 455 assertNoTasks(display); 456 457 final ActivityStarter starter = prepareStarter(0); 458 459 final LockTaskController lockTaskController = mService.getLockTaskController(); 460 doReturn(true).when(lockTaskController).isLockTaskModeViolation(any()); 461 462 final int result = starter.setReason("testTaskModeViolation").execute(); 463 464 assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result); 465 assertNoTasks(display); 466 } 467 468 private void assertNoTasks(ActivityDisplay display) { 469 for (int i = display.getChildCount() - 1; i >= 0; --i) { 470 final ActivityStack stack = display.getChildAt(i); 471 assertTrue(stack.getAllTasks().isEmpty()); 472 } 473 } 474} 475