• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.content.Context.ACTIVITY_SERVICE;
21 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
22 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
23 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
24 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 
30 import android.app.Activity;
31 import android.app.ActivityManager;
32 import android.app.ActivityOptions;
33 import android.app.Instrumentation;
34 import android.app.stubs.MockActivity;
35 import android.app.stubs.MockListActivity;
36 import android.app.stubs.MockTopResumedReporterActivity;
37 import android.content.ComponentName;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.os.SystemClock;
41 import android.util.Log;
42 
43 import androidx.test.InstrumentationRegistry;
44 import androidx.test.filters.FlakyTest;
45 import androidx.test.filters.MediumTest;
46 import androidx.test.rule.ActivityTestRule;
47 import androidx.test.runner.AndroidJUnit4;
48 import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
49 import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
50 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
51 import androidx.test.runner.lifecycle.Stage;
52 
53 import com.android.compatibility.common.util.ApiTest;
54 
55 import org.junit.Before;
56 import org.junit.Rule;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 import java.util.ArrayList;
61 import java.util.List;
62 import java.util.function.BooleanSupplier;
63 
64 /**
65  * atest CtsAppTestCases:AppTaskTests
66  */
67 @MediumTest
68 @FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
69 @RunWith(AndroidJUnit4.class)
70 public class AppTaskTests {
71 
72     private static final String TAG = AppTaskTests.class.getSimpleName();
73 
74     private static final long TIME_SLICE_MS = 1000;
75     private static final long WAIT_RETRIES = 5;
76 
77     private static final String EXTRA_KEY = "key";
78     private static final String EXTRA_VALUE = "some_value";
79 
80     private Instrumentation mInstrumentation;
81     private ActivityLifecycleMonitor mLifecycleMonitor;
82     private Context mTargetContext;
83 
84     @Rule
85     public ActivityTestRule<MockActivity> mActivityRule =
86             new ActivityTestRule<MockActivity>(
87                     MockActivity.class, false /* initialTouchMode */, false /* launchActivity */) {
88         @Override
89         public Intent getActivityIntent() {
90             Intent intent = new Intent();
91             intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT
92                     | FLAG_ACTIVITY_MULTIPLE_TASK);
93             intent.putExtra(EXTRA_KEY, EXTRA_VALUE);
94             return intent;
95         }
96     };
97 
98     @Before
setUp()99     public void setUp() throws Exception {
100         mInstrumentation = InstrumentationRegistry.getInstrumentation();
101         mLifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance();
102         mTargetContext = mInstrumentation.getTargetContext();
103         removeAllAppTasks();
104     }
105 
106     /**
107      * Launch an activity and ensure it is in the app task list.
108      */
109     @Test
testSingleAppTask()110     public void testSingleAppTask() throws Exception {
111         final Activity a1 = mActivityRule.launchActivity(null);
112         final List<ActivityManager.AppTask> appTasks = getAppTasks();
113         assertTrue(appTasks.size() == 1);
114         assertTrue(appTasks.get(0).getTaskInfo().topActivity.equals(a1.getComponentName()));
115     }
116 
117     /**
118      * Launch a couple tasks and ensure they are in the app tasks list.
119      */
120     @Test
testMultipleAppTasks()121     public void testMultipleAppTasks() throws Exception {
122         final ArrayList<Activity> activities = new ArrayList<>();
123         for (int i = 0; i < 5; i++) {
124             activities.add(mActivityRule.launchActivity(null));
125         }
126         final List<ActivityManager.AppTask> appTasks = getAppTasks();
127         assertTrue(appTasks.size() == activities.size());
128         for (int i = 0; i < appTasks.size(); i++) {
129             assertTrue(appTasks.get(i).getTaskInfo().topActivity.equals(
130                     activities.get(i).getComponentName()));
131         }
132     }
133 
134     /**
135      * Remove an app task and ensure that it is actually removed.
136      */
137     @Test
testFinishAndRemoveTask()138     public void testFinishAndRemoveTask() throws Exception {
139         final Activity a1 = mActivityRule.launchActivity(null);
140         waitAndAssertCondition(() -> getAppTasks().size() == 1, "Expected 1 running task");
141         getAppTask(a1).finishAndRemoveTask();
142         waitAndAssertCondition(() -> getAppTasks().isEmpty(), "Expected no running tasks");
143     }
144 
145     /**
146      * Ensure that moveToFront will bring the first activity forward.
147      */
148     @Test
testMoveToFront()149     public void testMoveToFront() throws Exception {
150         final Intent firstActivityIntent = new Intent();
151         firstActivityIntent.setComponent(
152                 new ComponentName(mTargetContext, MockTopResumedReporterActivity.class));
153         firstActivityIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
154         MockTopResumedReporterActivity firstActivity =
155                 (MockTopResumedReporterActivity) mInstrumentation.startActivitySync(
156                         firstActivityIntent);
157 
158         // Launch fullscreen activity as an another task to hide the first activity
159         final Intent secondActivityIntent = new Intent();
160         secondActivityIntent.setComponent(new ComponentName(mTargetContext, MockActivity.class));
161         secondActivityIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT
162                     | FLAG_ACTIVITY_MULTIPLE_TASK);
163         final ActivityOptions options = ActivityOptions.makeBasic();
164         options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
165         mInstrumentation.startActivitySync(secondActivityIntent, options.toBundle());
166         waitAndAssertCondition(() -> !firstActivity.isTopResumed,
167                 "First activity is not top resumed");
168         final BooleanValue targetResumed = new BooleanValue();
169         mLifecycleMonitor.addLifecycleCallback(new ActivityLifecycleCallback() {
170             public void onActivityLifecycleChanged(Activity activity, Stage stage) {
171                 if (activity == firstActivity && stage == Stage.RESUMED) {
172                     targetResumed.value = true;
173                 }
174             }
175         });
176 
177         final ActivityManager.AppTask appTask = getAppTask(firstActivity);
178         appTask.moveToFront();
179         // Different form factors may force tasks to be multi-window (e.g. in freeform windowing
180         // mode). a1 would still be resumed and visible without a lifecycle change even when
181         // another activity is launched in this case.
182         if (!firstActivity.isInMultiWindowMode()) {
183             waitAndAssertCondition(() -> targetResumed.value,
184                     "Expected activity brought to front and resumed");
185         }
186         waitAndAssertCondition(() -> appTask.getTaskInfo().isVisible(), "Waiting for task visible");
187         assertEquals(appTask.getTaskInfo().topActivity, firstActivity.getComponentName());
188         waitAndAssertCondition(() -> firstActivity.isTopResumed,
189                 "First activity is top resumed");
190     }
191 
192     /**
193      * Ensure that starting a new activity in the same task results in two activities in the task.
194      */
195     @Test
testStartActivityInTask_NoNewTask()196     public void testStartActivityInTask_NoNewTask() throws Exception {
197         final Activity a1 = mActivityRule.launchActivity(null);
198         final ActivityManager.AppTask task = getAppTask(a1);
199         final Intent intent = new Intent();
200         intent.setComponent(new ComponentName(mTargetContext, MockListActivity.class));
201         task.startActivity(mTargetContext, intent, null);
202         waitAndAssertCondition(
203                 () -> getAppTask(a1) != null && getAppTask(a1).getTaskInfo().numActivities == 2,
204                 "Waiting for activity launch");
205 
206         final ActivityManager.RecentTaskInfo taskInfo = task.getTaskInfo();
207         assertTrue(taskInfo.numActivities == 2);
208         assertTrue(taskInfo.baseActivity.equals(a1.getComponentName()));
209         assertTrue(taskInfo.topActivity.equals(intent.getComponent()));
210     }
211 
212     /**
213      * Ensure that an activity with FLAG_ACTIVITY_NEW_TASK causes the task to be brought forward
214      * and the new activity not being started.
215      */
216     @Test
testStartActivityInTask_NewTask()217     public void testStartActivityInTask_NewTask() throws Exception {
218         final Activity a1 = mActivityRule.launchActivity(null);
219         final ActivityManager.AppTask task = getAppTask(a1);
220         final Intent intent = new Intent();
221         intent.setComponent(new ComponentName(mTargetContext, MockActivity.class));
222         intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
223         task.startActivity(mTargetContext, intent, null);
224 
225         final ActivityManager.RecentTaskInfo taskInfo = task.getTaskInfo();
226         assertTrue(taskInfo.numActivities == 1);
227         assertTrue(taskInfo.baseActivity.equals(a1.getComponentName()));
228     }
229 
230     /**
231      * Ensure that the activity that is excluded from recents is reflected in the recent task info.
232      */
233     @Test
testSetExcludeFromRecents()234     public void testSetExcludeFromRecents() throws Exception {
235         final Activity a1 = mActivityRule.launchActivity(null);
236         final ActivityManager.AppTask t1 = getAppTask(a1);
237 
238         t1.setExcludeFromRecents(true);
239         assertTrue((t1.getTaskInfo().baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
240                 != 0);
241         t1.setExcludeFromRecents(false);
242         assertTrue((t1.getTaskInfo().baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
243                 == 0);
244     }
245 
246     /**
247      * Ensure that the activity has the extras from the base intent.
248      */
249     @Test
250     @ApiTest(apis = {"android.app.ActivityManager.AppTask#getTaskInfo"})
testBaseIntentHasExtras()251     public void testBaseIntentHasExtras() throws Exception {
252         final Activity a1 = mActivityRule.launchActivity(null);
253         final List<ActivityManager.AppTask> appTasks = getAppTasks();
254         final ActivityManager.AppTask t1 = appTasks.get(0);
255         assertTrue(t1.getTaskInfo().baseIntent.getStringExtra(EXTRA_KEY).equals(EXTRA_VALUE));
256     }
257 
258     /**
259      * @return all the {@param ActivityManager.AppTask}s for the current app.
260      */
getAppTasks()261     private List<ActivityManager.AppTask> getAppTasks() {
262         ActivityManager am = (ActivityManager) mTargetContext.getSystemService(ACTIVITY_SERVICE);
263         return am.getAppTasks();
264     }
265 
266     /**
267      * @return the {@param ActivityManager.AppTask} for the associated activity.
268      */
getAppTask(Activity activity)269     private ActivityManager.AppTask getAppTask(Activity activity) {
270         waitAndAssertCondition(() -> getAppTask(getAppTasks(), activity) != null,
271                 "Waiting for app task");
272         return getAppTask(getAppTasks(), activity);
273     }
274 
getAppTask(List<ActivityManager.AppTask> appTasks, Activity activity)275     private ActivityManager.AppTask getAppTask(List<ActivityManager.AppTask> appTasks,
276             Activity activity) {
277         for (ActivityManager.AppTask task : appTasks) {
278             if (task.getTaskInfo().taskId == activity.getTaskId()) {
279                 return task;
280             }
281         }
282         return null;
283     }
284 
285     /**
286      * Removes all the app tasks the test app.
287      */
removeAllAppTasks()288     private void removeAllAppTasks() {
289         final List<ActivityManager.AppTask> appTasks = getAppTasks();
290         for (ActivityManager.AppTask task : appTasks) {
291             try {
292                 task.finishAndRemoveTask();
293             } catch (IllegalArgumentException e) {
294                 // There may be a timing issue that the task is removing.
295                 Log.w(TAG, "Task=" + task + " does not exist.");
296             }
297         }
298         waitAndAssertCondition(() -> getAppTasks().isEmpty(),
299                 "Expected no app tasks after all removed");
300     }
301 
waitAndAssertCondition(BooleanSupplier condition, String failMsgContext)302     private void waitAndAssertCondition(BooleanSupplier condition, String failMsgContext) {
303         long count = 0;
304         while (true) {
305             if (condition.getAsBoolean()) {
306                 // Condition passed
307                 return;
308             } else if (count >= (WAIT_RETRIES)) {
309                 // Timed out
310                 fail("Timed out waiting for: " + failMsgContext);
311             } else {
312                 count++;
313                 SystemClock.sleep(TIME_SLICE_MS);
314             }
315         }
316     }
317 
318     private class BooleanValue {
319         boolean value;
320     }
321 }
322