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