• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.os.Build.HW_TIMEOUT_MULTIPLIER;
20 
21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
22 
23 import static com.android.server.wm.utils.CommonUtils.runWithShellPermissionIdentity;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 
28 import android.app.Activity;
29 import android.app.ActivityManager.RunningTaskInfo;
30 import android.app.ActivityManager.TaskDescription;
31 import android.app.ActivityOptions;
32 import android.app.ActivityTaskManager;
33 import android.app.ITaskStackListener;
34 import android.app.Instrumentation.ActivityMonitor;
35 import android.app.TaskStackListener;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.pm.ActivityInfo;
40 import android.graphics.Color;
41 import android.hardware.display.VirtualDisplay;
42 import android.os.Bundle;
43 import android.os.RemoteException;
44 import android.os.SystemClock;
45 import android.platform.test.annotations.Presubmit;
46 import android.text.TextUtils;
47 import android.view.Display;
48 import android.view.ViewGroup;
49 import android.widget.LinearLayout;
50 
51 import androidx.test.filters.MediumTest;
52 
53 import com.android.server.wm.utils.CommonUtils;
54 import com.android.server.wm.utils.VirtualDisplayTestRule;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Rule;
59 import org.junit.Test;
60 
61 import java.util.ArrayList;
62 import java.util.concurrent.ArrayBlockingQueue;
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.TimeUnit;
65 import java.util.function.Predicate;
66 
67 /**
68  * Build/Install/Run:
69  *  atest WmTests:TaskStackChangedListenerTest
70  */
71 @MediumTest
72 public class TaskStackChangedListenerTest {
73 
74     @Rule
75     public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule();
76     private ITaskStackListener mTaskStackListener;
77     private final ArrayList<Activity> mStartedActivities = new ArrayList<>();
78 
79     private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER;
80     private static final Object sLock = new Object();
81 
82     @Before
setUp()83     public void setUp() {
84         CommonUtils.dismissKeyguard();
85     }
86 
87     @After
tearDown()88     public void tearDown() throws Exception {
89         if (mTaskStackListener != null) {
90             ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
91         }
92         // Finish from bottom to top.
93         final int size = mStartedActivities.size();
94         for (int i = 0; i < size; i++) {
95             final Activity activity = mStartedActivities.get(i);
96             if (!activity.isFinishing()) {
97                 activity.finish();
98             }
99         }
100         // Wait for the last launched activity to be removed.
101         if (size > 0) {
102             CommonUtils.waitUntilActivityRemoved(mStartedActivities.get(size - 1));
103         }
104         mStartedActivities.clear();
105     }
106 
createVirtualDisplay()107     private VirtualDisplay createVirtualDisplay() {
108         final int width = 800;
109         final int height = 600;
110         final String name = getClass().getSimpleName() + "_VirtualDisplay";
111         return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width,
112                 height);
113     }
114 
115     @Test
116     @Presubmit
testTaskStackChanged_afterFinish()117     public void testTaskStackChanged_afterFinish() throws Exception {
118         final TestActivity activity = startTestActivity(ActivityA.class);
119         final CountDownLatch latch = new CountDownLatch(1);
120         registerTaskStackChangedListener(new TaskStackListener() {
121             @Override
122             public void onTaskStackChanged() throws RemoteException {
123                 latch.countDown();
124             }
125         });
126 
127         activity.finish();
128         waitForCallback(latch);
129     }
130 
131     @Test
132     @Presubmit
testTaskStackChanged_resumeWhilePausing()133     public void testTaskStackChanged_resumeWhilePausing() throws Exception {
134         final CountDownLatch latch = new CountDownLatch(1);
135         registerTaskStackChangedListener(new TaskStackListener() {
136             @Override
137             public void onTaskStackChanged() throws RemoteException {
138                 latch.countDown();
139             }
140         });
141 
142         startTestActivity(ResumeWhilePausingActivity.class);
143         waitForCallback(latch);
144     }
145 
146     @Test
147     @Presubmit
testTaskDescriptionChanged()148     public void testTaskDescriptionChanged() throws Exception {
149         final Object[] params = new Object[2];
150         final CountDownLatch latch = new CountDownLatch(2);
151         registerTaskStackChangedListener(new TaskStackListener() {
152             int mTaskId = -1;
153 
154             @Override
155             public void onTaskCreated(int taskId, ComponentName componentName)
156                     throws RemoteException {
157                 mTaskId = taskId;
158             }
159             @Override
160             public void onTaskDescriptionChanged(RunningTaskInfo info) {
161                 if (mTaskId == info.taskId && !TextUtils.isEmpty(info.taskDescription.getLabel())) {
162                     params[0] = info.taskId;
163                     params[1] = info.taskDescription;
164                     latch.countDown();
165                 }
166             }
167         });
168 
169         int taskId;
170         synchronized (sLock) {
171             taskId = startTestActivity(ActivityTaskDescriptionChange.class).getTaskId();
172         }
173         waitForCallback(latch);
174         assertEquals(taskId, params[0]);
175         assertEquals("Test Label", ((TaskDescription) params[1]).getLabel());
176     }
177 
178     @Test
179     @Presubmit
testActivityRequestedOrientationChanged()180     public void testActivityRequestedOrientationChanged() throws Exception {
181         final int[] params = new int[2];
182         final CountDownLatch latch = new CountDownLatch(1);
183         registerTaskStackChangedListener(new TaskStackListener() {
184             @Override
185             public void onActivityRequestedOrientationChanged(int taskId,
186                     int requestedOrientation) {
187                 params[0] = taskId;
188                 params[1] = requestedOrientation;
189                 latch.countDown();
190             }
191         });
192         int taskId;
193         synchronized (sLock) {
194             taskId = startTestActivity(ActivityRequestedOrientationChange.class).getTaskId();
195         }
196         waitForCallback(latch);
197         assertEquals(taskId, params[0]);
198         assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]);
199     }
200 
201     /**
202      * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted.
203      */
204     @Test
205     @Presubmit
testTaskChangeCallBacks()206     public void testTaskChangeCallBacks() throws Exception {
207         final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1);
208         final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1);
209         final CountDownLatch taskRemovedLatch = new CountDownLatch(1);
210         final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1);
211         final int[] expectedTaskId = { -1 };
212         final int[] receivedTaskId = { -1 };
213         final ComponentName expectedName = new ComponentName(getInstrumentation().getContext(),
214                 ActivityTaskChangeCallbacks.class);
215         registerTaskStackChangedListener(new TaskStackListener() {
216             @Override
217             public void onTaskCreated(int taskId, ComponentName componentName) {
218                 receivedTaskId[0] = taskId;
219                 if (expectedName.equals(componentName)) {
220                     taskCreatedLaunchLatch.countDown();
221                 }
222             }
223 
224             @Override
225             public void onTaskMovedToFront(RunningTaskInfo info) {
226                 receivedTaskId[0] = info.taskId;
227                 taskMovedToFrontLatch.countDown();
228             }
229 
230             @Override
231             public void onTaskRemovalStarted(RunningTaskInfo info) {
232                 if (expectedTaskId[0] == info.taskId) {
233                     taskRemovalStartedLatch.countDown();
234                 }
235             }
236 
237             @Override
238             public void onTaskRemoved(int taskId) {
239                 if (expectedTaskId[0] == taskId) {
240                     taskRemovedLatch.countDown();
241                 }
242             }
243         });
244 
245         final ActivityTaskChangeCallbacks activity =
246                 (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class);
247         expectedTaskId[0] = activity.getTaskId();
248 
249         // Test for onTaskCreated and onTaskMovedToFront
250         waitForCallback(taskMovedToFrontLatch);
251         assertEquals(0, taskCreatedLaunchLatch.getCount());
252         assertEquals(expectedTaskId[0], receivedTaskId[0]);
253 
254         // Ensure that the window is attached before removal so there will be a detached callback.
255         waitForCallback(activity.mOnAttachedToWindowCountDownLatch);
256         // Test for onTaskRemovalStarted.
257         assertEquals(1, taskRemovalStartedLatch.getCount());
258         assertEquals(1, taskRemovedLatch.getCount());
259         activity.finishAndRemoveTask();
260         waitForCallback(taskRemovalStartedLatch);
261         // onTaskRemovalStarted happens before the activity's window is removed.
262         assertEquals(1, activity.mOnDetachedFromWindowCountDownLatch.getCount());
263 
264         // Test for onTaskRemoved.
265         waitForCallback(taskRemovedLatch);
266         waitForCallback(activity.mOnDetachedFromWindowCountDownLatch);
267     }
268 
269     @Test
testTaskDisplayChanged()270     public void testTaskDisplayChanged() throws Exception {
271         final int virtualDisplayId = createVirtualDisplay().getDisplay().getDisplayId();
272 
273         // Launch a Activity inside VirtualDisplay
274         CountDownLatch displayChangedLatch1 = new CountDownLatch(1);
275         final Object[] params1 = new Object[1];
276         registerTaskStackChangedListener(new TaskDisplayChangedListener(
277                 virtualDisplayId, params1, displayChangedLatch1));
278         ActivityOptions options1 = ActivityOptions.makeBasic().setLaunchDisplayId(virtualDisplayId);
279         int taskId1;
280         synchronized (sLock) {
281             taskId1 = startTestActivity(ActivityInVirtualDisplay.class, options1).getTaskId();
282         }
283         waitForCallback(displayChangedLatch1);
284 
285         assertEquals(taskId1, params1[0]);
286 
287         // Launch the Activity in the default display, expects that reparenting happens.
288         final Object[] params2 = new Object[1];
289         final CountDownLatch displayChangedLatch2 = new CountDownLatch(1);
290         registerTaskStackChangedListener(
291                 new TaskDisplayChangedListener(
292                         Display.DEFAULT_DISPLAY, params2, displayChangedLatch2));
293         int taskId2;
294         ActivityOptions options2 = ActivityOptions.makeBasic()
295                 .setLaunchDisplayId(Display.DEFAULT_DISPLAY);
296         synchronized (sLock) {
297             taskId2 = startTestActivity(ActivityInVirtualDisplay.class, options2).getTaskId();
298         }
299         waitForCallback(displayChangedLatch2);
300 
301         assertEquals(taskId2, params2[0]);
302         assertEquals(taskId1, taskId2);  // TaskId should be same since reparenting happens.
303     }
304 
305     private static class TaskDisplayChangedListener extends TaskStackListener {
306         private int mDisplayId;
307         private final Object[] mParams;
308         private final CountDownLatch mDisplayChangedLatch;
TaskDisplayChangedListener( int displayId, Object[] params, CountDownLatch displayChangedLatch)309         TaskDisplayChangedListener(
310                 int displayId, Object[] params, CountDownLatch displayChangedLatch) {
311             mDisplayId = displayId;
312             mParams = params;
313             mDisplayChangedLatch = displayChangedLatch;
314         }
315         @Override
onTaskDisplayChanged(int taskId, int displayId)316         public void onTaskDisplayChanged(int taskId, int displayId) throws RemoteException {
317             // Filter out the events for the uninterested displays.
318             // if (displayId != mDisplayId) return;
319             mParams[0] = taskId;
320             mDisplayChangedLatch.countDown();
321         }
322     };
323 
324     @Presubmit
325     @Test
testNotifyTaskRequestedOrientationChanged()326     public void testNotifyTaskRequestedOrientationChanged() throws Exception {
327         final ArrayBlockingQueue<int[]> taskIdAndOrientationQueue = new ArrayBlockingQueue<>(10);
328         registerTaskStackChangedListener(new TaskStackListener() {
329             @Override
330             public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) {
331                 int[] taskIdAndOrientation = new int[2];
332                 taskIdAndOrientation[0] = taskId;
333                 taskIdAndOrientation[1] = requestedOrientation;
334                 taskIdAndOrientationQueue.offer(taskIdAndOrientation);
335             }
336         });
337 
338         final boolean isIgnoringOrientationRequest =
339                 CommonUtils.getIgnoreOrientationRequest(Display.DEFAULT_DISPLAY);
340         if (isIgnoringOrientationRequest) {
341             CommonUtils.setIgnoreOrientationRequest(Display.DEFAULT_DISPLAY, false);
342         }
343 
344         try {
345             final LandscapeActivity activity =
346                     (LandscapeActivity) startTestActivity(LandscapeActivity.class);
347 
348             int[] taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
349                     candidate -> candidate[0] == activity.getTaskId());
350             assertNotNull(taskIdAndOrientation);
351             assertEquals(
352                     ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, taskIdAndOrientation[1]);
353 
354             activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
355             taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
356                     candidate -> candidate[0] == activity.getTaskId());
357             assertNotNull(taskIdAndOrientation);
358             assertEquals(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, taskIdAndOrientation[1]);
359 
360             activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
361             taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
362                     candidate -> candidate[0] == activity.getTaskId());
363             assertNotNull(taskIdAndOrientation);
364             assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, taskIdAndOrientation[1]);
365         } finally {
366             CommonUtils.setIgnoreOrientationRequest(
367                     Display.DEFAULT_DISPLAY, isIgnoringOrientationRequest);
368         }
369     }
370 
371     /**
372      * Starts the provided activity and returns the started instance.
373      */
startTestActivity(Class<?> activityClass)374     private TestActivity startTestActivity(Class<?> activityClass) throws InterruptedException {
375         return startTestActivity(activityClass, ActivityOptions.makeBasic());
376     }
377 
startTestActivity(Class<?> activityClass, ActivityOptions options)378     private TestActivity startTestActivity(Class<?> activityClass, ActivityOptions options)
379             throws InterruptedException {
380         final ActivityMonitor monitor = new ActivityMonitor(activityClass.getName(), null, false);
381         getInstrumentation().addMonitor(monitor);
382         final Context context = getInstrumentation().getContext();
383         runWithShellPermissionIdentity(() -> context.startActivity(
384                 new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
385                 options.toBundle()));
386         final TestActivity activity =
387                 (TestActivity) monitor.waitForActivityWithTimeout(WAIT_TIMEOUT_MS);
388         if (activity == null) {
389             throw new RuntimeException("Timed out waiting for Activity");
390         }
391         activity.waitForResumeStateChange(true);
392         mStartedActivities.add(activity);
393         return activity;
394     }
395 
registerTaskStackChangedListener(ITaskStackListener listener)396     private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception {
397         if (mTaskStackListener != null) {
398             ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
399         }
400         mTaskStackListener = listener;
401         ActivityTaskManager.getService().registerTaskStackListener(listener);
402     }
403 
waitForCallback(CountDownLatch latch)404     private void waitForCallback(CountDownLatch latch) {
405         try {
406             final boolean result = latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
407             if (!result) {
408                 throw new AssertionError("Timed out waiting for task stack change notification");
409             }
410         } catch (InterruptedException e) {
411         }
412     }
413 
waitForResult(ArrayBlockingQueue<T> queue, Predicate<T> predicate)414     private <T> T waitForResult(ArrayBlockingQueue<T> queue, Predicate<T> predicate) {
415         try {
416             final long timeout = SystemClock.uptimeMillis() + TimeUnit.SECONDS.toMillis(15);
417             T result;
418             do {
419                 result = queue.poll(timeout - SystemClock.uptimeMillis(), TimeUnit.MILLISECONDS);
420             } while (result != null && !predicate.test(result));
421             return result;
422         } catch (InterruptedException e) {
423             return null;
424         }
425     }
426 
427     public static class TestActivity extends Activity {
428         boolean mIsResumed = false;
429 
430         @Override
onPostResume()431         protected void onPostResume() {
432             super.onPostResume();
433             synchronized (this) {
434                 mIsResumed = true;
435                 notifyAll();
436             }
437         }
438 
439         @Override
onPause()440         protected void onPause() {
441             super.onPause();
442             synchronized (this) {
443                 mIsResumed = false;
444                 notifyAll();
445             }
446         }
447 
448         /**
449          * If isResumed is {@code true}, sleep the thread until the activity is resumed.
450          * if {@code false}, sleep the thread until the activity is paused.
451          */
452         @SuppressWarnings("WaitNotInLoop")
waitForResumeStateChange(boolean isResumed)453         public void waitForResumeStateChange(boolean isResumed) throws InterruptedException {
454             synchronized (this) {
455                 if (mIsResumed == isResumed) {
456                     return;
457                 }
458                 wait(WAIT_TIMEOUT_MS);
459             }
460             assertEquals("The activity resume state change timed out", isResumed, mIsResumed);
461         }
462     }
463 
464     public static class ActivityA extends TestActivity {}
465 
466     public static class ActivityB extends TestActivity {
467 
468         @Override
onPostResume()469         protected void onPostResume() {
470             super.onPostResume();
471             finish();
472         }
473     }
474 
475     public static class ActivityRequestedOrientationChange extends TestActivity {
476         @Override
onPostResume()477         protected void onPostResume() {
478             super.onPostResume();
479             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
480             synchronized (sLock) {
481                 // Hold the lock to ensure no one is trying to access fields of this Activity in
482                 // this test.
483                 finish();
484             }
485         }
486     }
487 
488     public static class ActivityTaskDescriptionChange extends TestActivity {
489         @Override
onPostResume()490         protected void onPostResume() {
491             super.onPostResume();
492             setTaskDescription(new TaskDescription("Test Label"));
493             // Sets the color of the status-bar should update the TaskDescription again.
494             getWindow().setStatusBarColor(Color.RED);
495             synchronized (sLock) {
496                 // Hold the lock to ensure no one is trying to access fields of this Activity in
497                 // this test.
498                 finish();
499             }
500         }
501     }
502 
503     public static class ActivityTaskChangeCallbacks extends TestActivity {
504         final CountDownLatch mOnAttachedToWindowCountDownLatch = new CountDownLatch(1);
505         final CountDownLatch mOnDetachedFromWindowCountDownLatch = new CountDownLatch(1);
506 
507         @Override
onAttachedToWindow()508         public void onAttachedToWindow() {
509             mOnAttachedToWindowCountDownLatch.countDown();
510         }
511 
512         @Override
onDetachedFromWindow()513         public void onDetachedFromWindow() {
514             mOnDetachedFromWindowCountDownLatch.countDown();
515         }
516     }
517 
518     public static class ActivityInVirtualDisplay extends TestActivity {
519 
520         @Override
onCreate(Bundle savedInstanceState)521         public void onCreate(Bundle savedInstanceState) {
522             super.onCreate(savedInstanceState);
523 
524             LinearLayout layout = new LinearLayout(this);
525             layout.setLayoutParams(new ViewGroup.LayoutParams(
526                     ViewGroup.LayoutParams.MATCH_PARENT,
527                     ViewGroup.LayoutParams.MATCH_PARENT));
528             setContentView(layout);
529         }
530     }
531 
532     public static class ResumeWhilePausingActivity extends TestActivity {}
533 
534     public static class LandscapeActivity extends TestActivity {}
535 }
536