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