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