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.app.cts;
18 
19 import static android.content.Context.ACTIVITY_SERVICE;
20 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
21 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
22 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
23 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
24 
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 
28 import android.app.Activity;
29 import android.app.ActivityManager;
30 import android.app.Instrumentation;
31 import android.app.stubs.MockActivity;
32 import android.app.stubs.MockListActivity;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.os.SystemClock;
37 
38 import androidx.test.InstrumentationRegistry;
39 import androidx.test.filters.FlakyTest;
40 import androidx.test.filters.MediumTest;
41 import androidx.test.rule.ActivityTestRule;
42 import androidx.test.runner.AndroidJUnit4;
43 import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
44 import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
45 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
46 import androidx.test.runner.lifecycle.Stage;
47 
48 import org.junit.Before;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.function.BooleanSupplier;
56 
57 /**
58  * atest CtsAppTestCases:AppTaskTests
59  */
60 @MediumTest
61 @FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
62 @RunWith(AndroidJUnit4.class)
63 public class AppTaskTests {
64 
65     private static final long TIME_SLICE_MS = 100;
66     private static final long MAX_WAIT_MS = 1500;
67 
68     private Instrumentation mInstrumentation;
69     private ActivityLifecycleMonitor mLifecycleMonitor;
70     private Context mTargetContext;
71 
72     @Rule
73     public ActivityTestRule<MockActivity> mActivityRule =
74             new ActivityTestRule<MockActivity>(MockActivity.class) {
75         @Override
76         public Intent getActivityIntent() {
77             Intent intent = new Intent();
78             intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT
79                     | FLAG_ACTIVITY_MULTIPLE_TASK);
80             return intent;
81         }
82     };
83 
84     @Before
setUp()85     public void setUp() throws Exception {
86         mInstrumentation = InstrumentationRegistry.getInstrumentation();
87         mLifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance();
88         mTargetContext = mInstrumentation.getTargetContext();
89         removeAllAppTasks();
90     }
91 
92     /**
93      * Launch an activity and ensure it is in the app task list.
94      */
95     @Test
testSingleAppTask()96     public void testSingleAppTask() throws Exception {
97         final Activity a1 = mActivityRule.launchActivity(null);
98         final List<ActivityManager.AppTask> appTasks = getAppTasks();
99         assertTrue(appTasks.size() == 1);
100         assertTrue(appTasks.get(0).getTaskInfo().topActivity.equals(a1.getComponentName()));
101     }
102 
103     /**
104      * Launch a couple tasks and ensure they are in the app tasks list.
105      */
106     @Test
testMultipleAppTasks()107     public void testMultipleAppTasks() throws Exception {
108         final ArrayList<Activity> activities = new ArrayList<>();
109         for (int i = 0; i < 5; i++) {
110             activities.add(mActivityRule.launchActivity(null));
111         }
112         final List<ActivityManager.AppTask> appTasks = getAppTasks();
113         assertTrue(appTasks.size() == activities.size());
114         for (int i = 0; i < appTasks.size(); i++) {
115             assertTrue(appTasks.get(i).getTaskInfo().topActivity.equals(
116                     activities.get(i).getComponentName()));
117         }
118     }
119 
120     /**
121      * Remove an app task and ensure that it is actually removed.
122      */
123     @Test
testFinishAndRemoveTask()124     public void testFinishAndRemoveTask() throws Exception {
125         final Activity a1 = mActivityRule.launchActivity(null);
126         waitAndAssertCondition(() -> getAppTasks().size() == 1, "Expected 1 running task");
127         getAppTask(a1).finishAndRemoveTask();
128         waitAndAssertCondition(() -> getAppTasks().isEmpty(), "Expected no running tasks");
129     }
130 
131     /**
132      * Ensure that moveToFront will bring the first activity forward.
133      */
134     @Test
testMoveToFront()135     public void testMoveToFront() throws Exception {
136         final Activity a1 = mActivityRule.launchActivity(null);
137         final Activity a2 = mActivityRule.launchActivity(null);
138         final BooleanValue targetResumed = new BooleanValue();
139         mLifecycleMonitor.addLifecycleCallback(new ActivityLifecycleCallback() {
140             public void onActivityLifecycleChanged(Activity activity, Stage stage) {
141                 if (activity == a1 && stage == Stage.RESUMED) {
142                     targetResumed.value = true;
143                 }
144             }
145         });
146 
147         getAppTask(a1).moveToFront();
148         waitAndAssertCondition(() -> targetResumed.value,
149                 "Expected activity brought to front and resumed");
150     }
151 
152     /**
153      * Ensure that starting a new activity in the same task results in two activities in the task.
154      */
155     @Test
testStartActivityInTask_NoNewTask()156     public void testStartActivityInTask_NoNewTask() throws Exception {
157         final Activity a1 = mActivityRule.launchActivity(null);
158         final ActivityManager.AppTask task = getAppTask(a1);
159         final Intent intent = new Intent();
160         intent.setComponent(new ComponentName(mTargetContext, MockListActivity.class));
161         task.startActivity(mTargetContext, intent, null);
162         waitAndAssertCondition(
163                 () -> getAppTask(a1) != null && getAppTask(a1).getTaskInfo().numActivities == 2,
164                 "Waiting for activity launch");
165 
166         final ActivityManager.RecentTaskInfo taskInfo = task.getTaskInfo();
167         assertTrue(taskInfo.numActivities == 2);
168         assertTrue(taskInfo.baseActivity.equals(a1.getComponentName()));
169         assertTrue(taskInfo.topActivity.equals(intent.getComponent()));
170     }
171 
172     /**
173      * Ensure that an activity with FLAG_ACTIVITY_NEW_TASK causes the task to be brought forward
174      * and the new activity not being started.
175      */
176     @Test
testStartActivityInTask_NewTask()177     public void testStartActivityInTask_NewTask() throws Exception {
178         final Activity a1 = mActivityRule.launchActivity(null);
179         final ActivityManager.AppTask task = getAppTask(a1);
180         final Intent intent = new Intent();
181         intent.setComponent(new ComponentName(mTargetContext, MockActivity.class));
182         intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
183         task.startActivity(mTargetContext, intent, null);
184 
185         final ActivityManager.RecentTaskInfo taskInfo = task.getTaskInfo();
186         assertTrue(taskInfo.numActivities == 1);
187         assertTrue(taskInfo.baseActivity.equals(a1.getComponentName()));
188     }
189 
190     /**
191      * Ensure that the activity that is excluded from recents is reflected in the recent task info.
192      */
193     @Test
testSetExcludeFromRecents()194     public void testSetExcludeFromRecents() throws Exception {
195         final Activity a1 = mActivityRule.launchActivity(null);
196         final List<ActivityManager.AppTask> appTasks = getAppTasks();
197         final ActivityManager.AppTask t1 = appTasks.get(0);
198         t1.setExcludeFromRecents(true);
199         assertTrue((t1.getTaskInfo().baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
200                 != 0);
201         t1.setExcludeFromRecents(false);
202         assertTrue((t1.getTaskInfo().baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
203                 == 0);
204     }
205 
206     /**
207      * @return all the {@param ActivityManager.AppTask}s for the current app.
208      */
getAppTasks()209     private List<ActivityManager.AppTask> getAppTasks() {
210         ActivityManager am = (ActivityManager) mTargetContext.getSystemService(ACTIVITY_SERVICE);
211         return am.getAppTasks();
212     }
213 
214     /**
215      * @return the {@param ActivityManager.AppTask} for the associated activity.
216      */
getAppTask(Activity activity)217     private ActivityManager.AppTask getAppTask(Activity activity) {
218         waitAndAssertCondition(() -> getAppTask(getAppTasks(), activity) != null,
219                 "Waiting for app task");
220         return getAppTask(getAppTasks(), activity);
221     }
222 
getAppTask(List<ActivityManager.AppTask> appTasks, Activity activity)223     private ActivityManager.AppTask getAppTask(List<ActivityManager.AppTask> appTasks,
224             Activity activity) {
225         for (ActivityManager.AppTask task : appTasks) {
226             if (task.getTaskInfo().taskId == activity.getTaskId()) {
227                 return task;
228             }
229         }
230         return null;
231     }
232 
233     /**
234      * Removes all the app tasks the test app.
235      */
removeAllAppTasks()236     private void removeAllAppTasks() {
237         final List<ActivityManager.AppTask> appTasks = getAppTasks();
238         for (ActivityManager.AppTask task : appTasks) {
239             task.finishAndRemoveTask();
240         }
241         waitAndAssertCondition(() -> getAppTasks().isEmpty(),
242                 "Expected no app tasks after all removed");
243     }
244 
waitAndAssertCondition(BooleanSupplier condition, String failMsgContext)245     private void waitAndAssertCondition(BooleanSupplier condition, String failMsgContext) {
246         long startTime = SystemClock.elapsedRealtime();
247         while (true) {
248             if (condition.getAsBoolean()) {
249                 // Condition passed
250                 return;
251             } else if (SystemClock.elapsedRealtime() > (startTime + MAX_WAIT_MS)) {
252                 // Timed out
253                 fail("Timed out waiting for: " + failMsgContext);
254             } else {
255                 SystemClock.sleep(TIME_SLICE_MS);
256             }
257         }
258     }
259 
260     private class BooleanValue {
261         boolean value;
262     }
263 }