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