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