1/* 2 * Copyright (C) 2016 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 17package com.android.server.am; 18 19import static android.support.test.InstrumentationRegistry.getInstrumentation; 20import static org.junit.Assert.assertEquals; 21import static org.junit.Assert.assertFalse; 22import static org.junit.Assert.assertTrue; 23 24import android.app.Activity; 25import android.app.ActivityManager; 26import android.app.ActivityManager.TaskDescription; 27import android.app.IActivityManager; 28import android.app.ITaskStackListener; 29import android.app.Instrumentation.ActivityMonitor; 30import android.app.TaskStackListener; 31import android.content.ComponentName; 32import android.content.Context; 33import android.content.Intent; 34import android.content.pm.ActivityInfo; 35import android.content.res.Resources.Theme; 36import android.os.RemoteException; 37import android.support.test.InstrumentationRegistry; 38import android.support.test.filters.MediumTest; 39import android.support.test.runner.AndroidJUnit4; 40import android.support.test.uiautomator.UiDevice; 41import android.text.TextUtils; 42import android.util.Pair; 43import com.android.internal.annotations.GuardedBy; 44import java.util.concurrent.CountDownLatch; 45import java.util.concurrent.TimeUnit; 46import org.junit.After; 47import org.junit.Assert; 48import org.junit.Before; 49import org.junit.Test; 50import org.junit.runner.RunWith; 51 52@MediumTest 53@RunWith(AndroidJUnit4.class) 54public class TaskStackChangedListenerTest { 55 56 private IActivityManager mService; 57 private ITaskStackListener mTaskStackListener; 58 59 private static final Object sLock = new Object(); 60 @GuardedBy("sLock") 61 private static boolean sTaskStackChangedCalled; 62 private static boolean sActivityBResumed; 63 64 @Before 65 public void setUp() throws Exception { 66 mService = ActivityManager.getService(); 67 } 68 69 @After 70 public void tearDown() throws Exception { 71 mService.unregisterTaskStackListener(mTaskStackListener); 72 mTaskStackListener = null; 73 } 74 75 @Test 76 public void testTaskStackChanged_afterFinish() throws Exception { 77 registerTaskStackChangedListener(new TaskStackListener() { 78 @Override 79 public void onTaskStackChanged() throws RemoteException { 80 synchronized (sLock) { 81 sTaskStackChangedCalled = true; 82 } 83 } 84 }); 85 86 Context ctx = InstrumentationRegistry.getContext(); 87 ctx.startActivity(new Intent(ctx, ActivityA.class)); 88 UiDevice.getInstance(getInstrumentation()).waitForIdle(); 89 synchronized (sLock) { 90 Assert.assertTrue(sTaskStackChangedCalled); 91 } 92 Assert.assertTrue(sActivityBResumed); 93 } 94 95 @Test 96 public void testTaskDescriptionChanged() throws Exception { 97 final Object[] params = new Object[2]; 98 final CountDownLatch latch = new CountDownLatch(1); 99 registerTaskStackChangedListener(new TaskStackListener() { 100 int taskId = -1; 101 102 @Override 103 public void onTaskCreated(int taskId, ComponentName componentName) 104 throws RemoteException { 105 this.taskId = taskId; 106 } 107 @Override 108 public void onTaskDescriptionChanged(int taskId, TaskDescription td) 109 throws RemoteException { 110 if (this.taskId == taskId && !TextUtils.isEmpty(td.getLabel())) { 111 params[0] = taskId; 112 params[1] = td; 113 latch.countDown(); 114 } 115 } 116 }); 117 final Activity activity = startTestActivity(ActivityTaskDescriptionChange.class); 118 waitForCallback(latch); 119 assertEquals(activity.getTaskId(), params[0]); 120 assertEquals("Test Label", ((TaskDescription) params[1]).getLabel()); 121 } 122 123 @Test 124 public void testActivityRequestedOrientationChanged() throws Exception { 125 final int[] params = new int[2]; 126 final CountDownLatch latch = new CountDownLatch(1); 127 registerTaskStackChangedListener(new TaskStackListener() { 128 @Override 129 public void onActivityRequestedOrientationChanged(int taskId, 130 int requestedOrientation) { 131 params[0] = taskId; 132 params[1] = requestedOrientation; 133 latch.countDown(); 134 } 135 }); 136 final Activity activity = startTestActivity(ActivityRequestedOrientationChange.class); 137 waitForCallback(latch); 138 assertEquals(activity.getTaskId(), params[0]); 139 assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]); 140 } 141 142 @Test 143 /** 144 * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted. 145 */ 146 public void testTaskChangeCallBacks() throws Exception { 147 final Object[] params = new Object[2]; 148 final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1); 149 final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1); 150 final CountDownLatch taskRemovedLatch = new CountDownLatch(1); 151 final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1); 152 final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1); 153 registerTaskStackChangedListener(new TaskStackListener() { 154 @Override 155 public void onTaskCreated(int taskId, ComponentName componentName) 156 throws RemoteException { 157 params[0] = taskId; 158 params[1] = componentName; 159 taskCreatedLaunchLatch.countDown(); 160 } 161 162 @Override 163 public void onTaskMovedToFront(int taskId) throws RemoteException { 164 params[0] = taskId; 165 taskMovedToFrontLatch.countDown(); 166 } 167 168 @Override 169 public void onTaskRemovalStarted(int taskId) { 170 params[0] = taskId; 171 taskRemovalStartedLatch.countDown(); 172 } 173 174 @Override 175 public void onTaskRemoved(int taskId) throws RemoteException { 176 params[0] = taskId; 177 taskRemovedLatch.countDown(); 178 } 179 }); 180 181 final ActivityTaskChangeCallbacks activity = 182 (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class); 183 final int id = activity.getTaskId(); 184 185 // Test for onTaskCreated. 186 waitForCallback(taskCreatedLaunchLatch); 187 assertEquals(id, params[0]); 188 ComponentName componentName = (ComponentName) params[1]; 189 assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName()); 190 191 // Test for onTaskMovedToFront. 192 assertEquals(1, taskMovedToFrontLatch.getCount()); 193 mService.moveTaskToFront(id, 0, null); 194 waitForCallback(taskMovedToFrontLatch); 195 assertEquals(activity.getTaskId(), params[0]); 196 197 // Test for onTaskRemovalStarted. 198 assertEquals(1, taskRemovalStartedLatch.getCount()); 199 activity.finishAndRemoveTask(); 200 waitForCallback(taskRemovalStartedLatch); 201 // onTaskRemovalStarted happens before the activity's window is removed. 202 assertFalse(activity.onDetachedFromWindowCalled); 203 assertEquals(id, params[0]); 204 205 // Test for onTaskRemoved. 206 assertEquals(1, taskRemovedLatch.getCount()); 207 waitForCallback(taskRemovedLatch); 208 assertEquals(id, params[0]); 209 assertTrue(activity.onDetachedFromWindowCalled); 210 } 211 212 /** 213 * Starts the provided activity and returns the started instance. 214 */ 215 private Activity startTestActivity(Class<?> activityClass) { 216 final Context context = InstrumentationRegistry.getContext(); 217 final ActivityMonitor monitor = 218 new ActivityMonitor(activityClass.getName(), null, false); 219 InstrumentationRegistry.getInstrumentation().addMonitor(monitor); 220 context.startActivity(new Intent(context, activityClass)); 221 final Activity activity = monitor.waitForActivityWithTimeout(1000); 222 if (activity == null) { 223 throw new RuntimeException("Timed out waiting for Activity"); 224 } 225 return activity; 226 } 227 228 private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception { 229 mTaskStackListener = listener; 230 mService.registerTaskStackListener(listener); 231 } 232 233 private void waitForCallback(CountDownLatch latch) { 234 try { 235 final boolean result = latch.await(2, TimeUnit.SECONDS); 236 if (!result) { 237 throw new RuntimeException("Timed out waiting for task stack change notification"); 238 } 239 }catch (InterruptedException e) {} 240 } 241 242 public static class ActivityA extends Activity { 243 244 private boolean mActivityBLaunched = false; 245 246 @Override 247 protected void onPostResume() { 248 super.onPostResume(); 249 if (mActivityBLaunched) { 250 return; 251 } 252 mActivityBLaunched = true; 253 finish(); 254 startActivity(new Intent(this, ActivityB.class)); 255 } 256 } 257 258 public static class ActivityB extends Activity { 259 260 @Override 261 protected void onPostResume() { 262 super.onPostResume(); 263 synchronized (sLock) { 264 sTaskStackChangedCalled = false; 265 } 266 sActivityBResumed = true; 267 finish(); 268 } 269 } 270 271 public static class ActivityRequestedOrientationChange extends Activity { 272 @Override 273 protected void onPostResume() { 274 super.onPostResume(); 275 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 276 finish(); 277 } 278 } 279 280 public static class ActivityTaskDescriptionChange extends Activity { 281 @Override 282 protected void onPostResume() { 283 super.onPostResume(); 284 setTaskDescription(new TaskDescription("Test Label")); 285 finish(); 286 } 287 } 288 289 public static class ActivityTaskChangeCallbacks extends Activity { 290 public boolean onDetachedFromWindowCalled = false;; 291 292 @Override 293 public void onDetachedFromWindow() { 294 onDetachedFromWindowCalled = true; 295 } 296 } 297} 298