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