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 com.android.server.wm; 18 19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertTrue; 24 25 import android.app.Activity; 26 import android.app.ActivityManager; 27 import android.app.ActivityManager.TaskDescription; 28 import android.app.ActivityTaskManager; 29 import android.app.IActivityManager; 30 import android.app.ITaskStackListener; 31 import android.app.Instrumentation.ActivityMonitor; 32 import android.app.TaskStackListener; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.pm.ActivityInfo; 37 import android.os.RemoteException; 38 import android.platform.test.annotations.Presubmit; 39 import android.support.test.uiautomator.UiDevice; 40 import android.text.TextUtils; 41 42 import androidx.test.filters.FlakyTest; 43 import androidx.test.filters.MediumTest; 44 45 import com.android.internal.annotations.GuardedBy; 46 47 import org.junit.After; 48 import org.junit.Before; 49 import org.junit.Test; 50 51 import java.util.concurrent.CountDownLatch; 52 import java.util.concurrent.TimeUnit; 53 54 /** 55 * Build/Install/Run: 56 * atest WmTests:TaskStackChangedListenerTest 57 */ 58 @MediumTest 59 public class TaskStackChangedListenerTest { 60 61 private IActivityManager mService; 62 private ITaskStackListener mTaskStackListener; 63 64 private static final Object sLock = new Object(); 65 @GuardedBy("sLock") 66 private static boolean sTaskStackChangedCalled; 67 private static boolean sActivityBResumed; 68 69 @Before setUp()70 public void setUp() throws Exception { 71 mService = ActivityManager.getService(); 72 } 73 74 @After tearDown()75 public void tearDown() throws Exception { 76 ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener); 77 mTaskStackListener = null; 78 } 79 80 @Test 81 @Presubmit 82 @FlakyTest(bugId = 130388819) testTaskStackChanged_afterFinish()83 public void testTaskStackChanged_afterFinish() throws Exception { 84 registerTaskStackChangedListener(new TaskStackListener() { 85 @Override 86 public void onTaskStackChanged() throws RemoteException { 87 synchronized (sLock) { 88 sTaskStackChangedCalled = true; 89 } 90 } 91 }); 92 93 Context context = getInstrumentation().getContext(); 94 context.startActivity( 95 new Intent(context, ActivityA.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 96 UiDevice.getInstance(getInstrumentation()).waitForIdle(); 97 synchronized (sLock) { 98 assertTrue(sTaskStackChangedCalled); 99 } 100 assertTrue(sActivityBResumed); 101 } 102 103 @Test 104 @Presubmit testTaskDescriptionChanged()105 public void testTaskDescriptionChanged() throws Exception { 106 final Object[] params = new Object[2]; 107 final CountDownLatch latch = new CountDownLatch(1); 108 registerTaskStackChangedListener(new TaskStackListener() { 109 int mTaskId = -1; 110 111 @Override 112 public void onTaskCreated(int taskId, ComponentName componentName) 113 throws RemoteException { 114 mTaskId = taskId; 115 } 116 @Override 117 public void onTaskDescriptionChanged(int taskId, TaskDescription td) 118 throws RemoteException { 119 if (mTaskId == taskId && !TextUtils.isEmpty(td.getLabel())) { 120 params[0] = taskId; 121 params[1] = td; 122 latch.countDown(); 123 } 124 } 125 }); 126 127 int taskId; 128 synchronized (sLock) { 129 taskId = startTestActivity(ActivityTaskDescriptionChange.class).getTaskId(); 130 } 131 waitForCallback(latch); 132 assertEquals(taskId, params[0]); 133 assertEquals("Test Label", ((TaskDescription) params[1]).getLabel()); 134 } 135 136 @Test 137 @Presubmit testActivityRequestedOrientationChanged()138 public void testActivityRequestedOrientationChanged() throws Exception { 139 final int[] params = new int[2]; 140 final CountDownLatch latch = new CountDownLatch(1); 141 registerTaskStackChangedListener(new TaskStackListener() { 142 @Override 143 public void onActivityRequestedOrientationChanged(int taskId, 144 int requestedOrientation) { 145 params[0] = taskId; 146 params[1] = requestedOrientation; 147 latch.countDown(); 148 } 149 }); 150 int taskId; 151 synchronized (sLock) { 152 taskId = startTestActivity(ActivityRequestedOrientationChange.class).getTaskId(); 153 } 154 waitForCallback(latch); 155 assertEquals(taskId, params[0]); 156 assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]); 157 } 158 159 /** 160 * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted. 161 */ 162 @Test 163 @Presubmit 164 @FlakyTest(bugId = 130388819) testTaskChangeCallBacks()165 public void testTaskChangeCallBacks() throws Exception { 166 final Object[] params = new Object[2]; 167 final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1); 168 final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1); 169 final CountDownLatch taskRemovedLatch = new CountDownLatch(1); 170 final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1); 171 final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1); 172 registerTaskStackChangedListener(new TaskStackListener() { 173 @Override 174 public void onTaskCreated(int taskId, ComponentName componentName) 175 throws RemoteException { 176 params[0] = taskId; 177 params[1] = componentName; 178 taskCreatedLaunchLatch.countDown(); 179 } 180 181 @Override 182 public void onTaskMovedToFront(int taskId) throws RemoteException { 183 params[0] = taskId; 184 taskMovedToFrontLatch.countDown(); 185 } 186 187 @Override 188 public void onTaskRemovalStarted(int taskId) { 189 params[0] = taskId; 190 taskRemovalStartedLatch.countDown(); 191 } 192 193 @Override 194 public void onTaskRemoved(int taskId) throws RemoteException { 195 params[0] = taskId; 196 taskRemovedLatch.countDown(); 197 } 198 }); 199 200 final ActivityTaskChangeCallbacks activity = 201 (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class); 202 activity.setDetachedFromWindowLatch(onDetachedFromWindowLatch); 203 final int id = activity.getTaskId(); 204 205 // Test for onTaskCreated. 206 waitForCallback(taskCreatedLaunchLatch); 207 assertEquals(id, params[0]); 208 ComponentName componentName = (ComponentName) params[1]; 209 assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName()); 210 211 // Test for onTaskMovedToFront. 212 assertEquals(1, taskMovedToFrontLatch.getCount()); 213 mService.moveTaskToFront(null, getInstrumentation().getContext().getPackageName(), id, 0, 214 null); 215 waitForCallback(taskMovedToFrontLatch); 216 assertEquals(activity.getTaskId(), params[0]); 217 218 // Test for onTaskRemovalStarted. 219 assertEquals(1, taskRemovalStartedLatch.getCount()); 220 assertEquals(1, taskRemovedLatch.getCount()); 221 activity.finishAndRemoveTask(); 222 waitForCallback(taskRemovalStartedLatch); 223 // onTaskRemovalStarted happens before the activity's window is removed. 224 assertFalse(activity.mOnDetachedFromWindowCalled); 225 assertEquals(id, params[0]); 226 227 // Test for onTaskRemoved. 228 waitForCallback(taskRemovedLatch); 229 assertEquals(id, params[0]); 230 waitForCallback(onDetachedFromWindowLatch); 231 assertTrue(activity.mOnDetachedFromWindowCalled); 232 } 233 234 /** 235 * Starts the provided activity and returns the started instance. 236 */ startTestActivity(Class<?> activityClass)237 private TestActivity startTestActivity(Class<?> activityClass) throws InterruptedException { 238 final ActivityMonitor monitor = new ActivityMonitor(activityClass.getName(), null, false); 239 getInstrumentation().addMonitor(monitor); 240 final Context context = getInstrumentation().getContext(); 241 context.startActivity( 242 new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 243 final TestActivity activity = (TestActivity) monitor.waitForActivityWithTimeout(1000); 244 if (activity == null) { 245 throw new RuntimeException("Timed out waiting for Activity"); 246 } 247 activity.waitForResumeStateChange(true); 248 return activity; 249 } 250 registerTaskStackChangedListener(ITaskStackListener listener)251 private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception { 252 mTaskStackListener = listener; 253 ActivityTaskManager.getService().registerTaskStackListener(listener); 254 } 255 waitForCallback(CountDownLatch latch)256 private void waitForCallback(CountDownLatch latch) { 257 try { 258 final boolean result = latch.await(4, TimeUnit.SECONDS); 259 if (!result) { 260 throw new RuntimeException("Timed out waiting for task stack change notification"); 261 } 262 } catch (InterruptedException e) { 263 } 264 } 265 266 public static class TestActivity extends Activity { 267 boolean mIsResumed = false; 268 269 @Override onPostResume()270 protected void onPostResume() { 271 super.onPostResume(); 272 synchronized (this) { 273 mIsResumed = true; 274 notifyAll(); 275 } 276 } 277 278 @Override onPause()279 protected void onPause() { 280 super.onPause(); 281 synchronized (this) { 282 mIsResumed = false; 283 notifyAll(); 284 } 285 } 286 287 /** 288 * If isResumed is {@code true}, sleep the thread until the activity is resumed. 289 * if {@code false}, sleep the thread until the activity is paused. 290 */ 291 @SuppressWarnings("WaitNotInLoop") waitForResumeStateChange(boolean isResumed)292 public void waitForResumeStateChange(boolean isResumed) throws InterruptedException { 293 synchronized (this) { 294 if (mIsResumed == isResumed) { 295 return; 296 } 297 wait(5000); 298 } 299 assertEquals("The activity resume state change timed out", isResumed, mIsResumed); 300 } 301 } 302 303 public static class ActivityA extends TestActivity { 304 305 private boolean mActivityBLaunched = false; 306 307 @Override onPostResume()308 protected void onPostResume() { 309 super.onPostResume(); 310 if (mActivityBLaunched) { 311 return; 312 } 313 mActivityBLaunched = true; 314 finish(); 315 startActivity(new Intent(this, ActivityB.class)); 316 } 317 } 318 319 public static class ActivityB extends TestActivity { 320 321 @Override onPostResume()322 protected void onPostResume() { 323 super.onPostResume(); 324 synchronized (sLock) { 325 sTaskStackChangedCalled = false; 326 } 327 sActivityBResumed = true; 328 finish(); 329 } 330 } 331 332 public static class ActivityRequestedOrientationChange extends TestActivity { 333 @Override onPostResume()334 protected void onPostResume() { 335 super.onPostResume(); 336 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 337 synchronized (sLock) { 338 // Hold the lock to ensure no one is trying to access fields of this Activity in 339 // this test. 340 finish(); 341 } 342 } 343 } 344 345 public static class ActivityTaskDescriptionChange extends TestActivity { 346 @Override onPostResume()347 protected void onPostResume() { 348 super.onPostResume(); 349 setTaskDescription(new TaskDescription("Test Label")); 350 synchronized (sLock) { 351 // Hold the lock to ensure no one is trying to access fields of this Activity in 352 // this test. 353 finish(); 354 } 355 } 356 } 357 358 public static class ActivityTaskChangeCallbacks extends TestActivity { 359 public boolean mOnDetachedFromWindowCalled = false; 360 private CountDownLatch mOnDetachedFromWindowCountDownLatch; 361 362 @Override onDetachedFromWindow()363 public void onDetachedFromWindow() { 364 mOnDetachedFromWindowCalled = true; 365 mOnDetachedFromWindowCountDownLatch.countDown(); 366 } 367 setDetachedFromWindowLatch(CountDownLatch countDownLatch)368 void setDetachedFromWindowLatch(CountDownLatch countDownLatch) { 369 mOnDetachedFromWindowCountDownLatch = countDownLatch; 370 } 371 } 372 } 373