1 /*
2  * Copyright (C) 2023 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 android.display.cts;
18 
19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
22 import static android.util.DisplayMetrics.DENSITY_HIGH;
23 import static android.util.DisplayMetrics.DENSITY_MEDIUM;
24 
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.fail;
28 
29 import android.app.ActivityManager;
30 import android.app.Instrumentation;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.hardware.display.DisplayManager;
34 import android.hardware.display.VirtualDisplay;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.Messenger;
40 import android.platform.test.annotations.AppModeSdkSandbox;
41 import android.util.Log;
42 import android.util.SparseArray;
43 
44 import androidx.annotation.GuardedBy;
45 import androidx.annotation.NonNull;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 
48 import com.android.compatibility.common.util.SystemUtil;
49 
50 import org.junit.After;
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.junit.runners.Parameterized;
55 import org.junit.runners.Parameterized.Parameter;
56 import org.junit.runners.Parameterized.Parameters;
57 
58 import java.util.Arrays;
59 import java.util.concurrent.CountDownLatch;
60 import java.util.concurrent.LinkedBlockingQueue;
61 import java.util.concurrent.TimeUnit;
62 
63 /**
64  * Tests that applications can receive display events correctly.
65  */
66 @RunWith(Parameterized.class)
67 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
68 public class DisplayEventTest {
69     private static final String TAG = "DisplayEventTest";
70 
71     private static final String NAME = TAG;
72     private static final int WIDTH = 720;
73     private static final int HEIGHT = 480;
74 
75     private static final int MESSAGE_LAUNCHED = 1;
76     private static final int MESSAGE_CALLBACK = 2;
77 
78     private static final int DISPLAY_ADDED = 1;
79     private static final int DISPLAY_CHANGED = 2;
80     private static final int DISPLAY_REMOVED = 3;
81 
82     private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100;
83     private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;
84 
85     private static final String TEST_PACKAGE = "android.app.stubs";
86     private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity";
87     private static final String TEST_DISPLAYS = "DISPLAYS";
88     private static final String TEST_MESSENGER = "MESSENGER";
89 
90     private final Object mLock = new Object();
91 
92     private Instrumentation mInstrumentation;
93     private Context mContext;
94     private DisplayManager mDisplayManager;
95     private ActivityManager mActivityManager;
96     private ActivityManager.OnUidImportanceListener mUidImportanceListener;
97     private CountDownLatch mLatchActivityLaunch;
98     private CountDownLatch mLatchActivityCached;
99     private HandlerThread mHandlerThread;
100     private Handler mHandler;
101     private Messenger mMessenger;
102     private int mPid;
103     private int mUid;
104 
105     /**
106      * Array of DisplayBundle. The test handler uses it to check if certain display events have
107      * been sent to DisplayEventActivity.
108      * Key: displayId of each new VirtualDisplay created by this test
109      * Value: DisplayBundle, storing the VirtualDisplay and its expected display events
110      *
111      * NOTE: The lock is required when adding and removing virtual displays. Otherwise it's not
112      * necessary to lock mDisplayBundles when accessing it from the test function.
113      */
114     @GuardedBy("mLock")
115     private SparseArray<DisplayBundle> mDisplayBundles;
116 
117     /**
118      * Helper class to store VirtualDisplay and its corresponding display events expected to be
119      * sent to DisplayEventActivity.
120      */
121     private static final class DisplayBundle {
122         private VirtualDisplay mVirtualDisplay;
123         private final int mDisplayId;
124 
125         // Display events we expect to receive before timeout
126         private final LinkedBlockingQueue<Integer> mExpectations;
127 
DisplayBundle(VirtualDisplay display)128         DisplayBundle(VirtualDisplay display) {
129             mVirtualDisplay = display;
130             mDisplayId = display.getDisplay().getDisplayId();
131             mExpectations = new LinkedBlockingQueue<>();
132         }
133 
releaseDisplay()134         public void releaseDisplay() {
135             if (mVirtualDisplay != null) {
136                 mVirtualDisplay.release();
137             }
138             mVirtualDisplay = null;
139         }
140 
141         /**
142          * Add the received display event from the test activity to the queue
143          *
144          * @param event The corresponding display event
145          */
addDisplayEvent(int event)146         public void addDisplayEvent(int event) {
147             Log.d(TAG, "Received " + mDisplayId + " " + event);
148             mExpectations.offer(event);
149         }
150 
151 
152         /**
153          * Assert that there isn't any unexpected display event from the test activity
154          */
assertNoDisplayEvents()155         public void assertNoDisplayEvents() {
156             try {
157                 assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
158             } catch (InterruptedException e) {
159                 throw new RuntimeException(e);
160             }
161         }
162 
163         /**
164          * Wait for the expected display event from the test activity
165          *
166          * @param expect The expected display event
167          */
waitDisplayEvent(int expect)168         public void waitDisplayEvent(int expect) {
169             while (true) {
170                 try {
171                     final Integer event;
172                     event = mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
173                     assertNotNull(event);
174                     if (expect == event) {
175                         Log.d(TAG, "Found    " + mDisplayId + " " + event);
176                         return;
177                     }
178                 } catch (InterruptedException e) {
179                     throw new RuntimeException(e);
180                 }
181             }
182         }
183     }
184 
185     /**
186      * How many virtual displays to create during the test
187      */
188     @Parameter(0)
189     public int mDisplayCount;
190 
191     /**
192      * True if running the test activity in cached mode
193      * False if running it in non-cached mode
194      */
195     @Parameter(1)
196     public boolean mCached;
197 
198     @Parameters(name = "#{index}: {0} {1}")
data()199     public static Iterable<? extends Object> data() {
200         return Arrays.asList(new Object[][]{
201                 {1, false}, {2, false}, {3, false}, {10, false},
202                 {1, true}, {2, true}, {3, true}, {10, true}
203         });
204     }
205 
206     private class TestHandler extends Handler {
TestHandler(Looper looper)207         TestHandler(Looper looper) {
208             super(looper);
209         }
210 
211         @Override
handleMessage(@onNull Message msg)212         public void handleMessage(@NonNull Message msg) {
213             switch (msg.what) {
214                 case MESSAGE_LAUNCHED:
215                     mPid = msg.arg1;
216                     mUid = msg.arg2;
217                     Log.d(TAG, "Launched " + mPid + " " + mUid);
218                     mLatchActivityLaunch.countDown();
219                     break;
220                 case MESSAGE_CALLBACK:
221                     synchronized (mLock) {
222                         // arg1: displayId
223                         DisplayBundle bundle = mDisplayBundles.get(msg.arg1);
224                         if (bundle != null) {
225                             // arg2: display event
226                             bundle.addDisplayEvent(msg.arg2);
227                         }
228                     }
229                     break;
230                 default:
231                     fail("Unexpected value: " + msg.what);
232                     break;
233             }
234         }
235     }
236 
237     @Before
setUp()238     public void setUp() throws Exception {
239         mInstrumentation = InstrumentationRegistry.getInstrumentation();
240         mContext = mInstrumentation.getContext();
241         mDisplayManager = mContext.getSystemService(DisplayManager.class);
242         mLatchActivityLaunch = new CountDownLatch(1);
243         mLatchActivityCached = new CountDownLatch(1);
244         mActivityManager = mContext.getSystemService(ActivityManager.class);
245         mUidImportanceListener = (uid, importance) -> {
246             if (uid == mUid && importance == IMPORTANCE_CACHED) {
247                 Log.d(TAG, "Listener " + uid + " becomes " + importance);
248                 mLatchActivityCached.countDown();
249             }
250         };
251         SystemUtil.runWithShellPermissionIdentity(() ->
252                 mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
253                         IMPORTANCE_CACHED));
254         mDisplayBundles = new SparseArray<>();
255         mHandlerThread = new HandlerThread("handler");
256         mHandlerThread.start();
257         mHandler = new TestHandler(mHandlerThread.getLooper());
258         mMessenger = new Messenger(mHandler);
259         mPid = 0;
260     }
261 
262     @After
tearDown()263     public void tearDown() throws Exception {
264         mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
265         mHandlerThread.quitSafely();
266         synchronized (mLock) {
267             for (int i = 0; i < mDisplayBundles.size(); i++) {
268                 DisplayBundle bundle = mDisplayBundles.valueAt(i);
269                 // Clean up unreleased virtual display
270                 bundle.releaseDisplay();
271             }
272             mDisplayBundles.clear();
273         }
274         SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE);
275     }
276 
277     /**
278      * Create virtual displays, change their configurations and release them
279      * mDisplays: the amount of virtual displays to be created
280      * mCached: true to run the test activity in cached mode; false in non-cached mode
281      */
282     @Test
testDisplayEvents()283     public void testDisplayEvents() {
284         Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + mCached);
285         // Launch DisplayEventActivity and start listening to display events
286         launchTestActivity();
287 
288         if (mCached) {
289             // The test activity in cached mode won't receive the pending display events
290             makeTestActivityCached();
291         }
292 
293         // Create new virtual displays
294         for (int i = 0; i < mDisplayCount; i++) {
295             // Lock is needed here to ensure the handler can query the displays
296             synchronized (mLock) {
297                 VirtualDisplay display = createVirtualDisplay(NAME + i);
298                 DisplayBundle bundle = new DisplayBundle(display);
299                 mDisplayBundles.put(bundle.mDisplayId, bundle);
300             }
301         }
302 
303         for (int i = 0; i < mDisplayCount; i++) {
304             if (mCached) {
305                 // DISPLAY_ADDED should be deferred for cached process
306                 mDisplayBundles.valueAt(i).assertNoDisplayEvents();
307             } else {
308                 // DISPLAY_ADDED should arrive immediately for non-cached process
309                 mDisplayBundles.valueAt(i).waitDisplayEvent(DISPLAY_ADDED);
310             }
311         }
312 
313         // Change the virtual displays
314         for (int i = 0; i < mDisplayCount; i++) {
315             DisplayBundle bundle = mDisplayBundles.valueAt(i);
316             bundle.mVirtualDisplay.resize(WIDTH, HEIGHT, DENSITY_HIGH);
317         }
318 
319         for (int i = 0; i < mDisplayCount; i++) {
320             if (mCached) {
321                 // DISPLAY_CHANGED should be deferred for cached process
322                 mDisplayBundles.valueAt(i).assertNoDisplayEvents();
323             } else {
324                 // DISPLAY_CHANGED should arrive immediately for non-cached process
325                 mDisplayBundles.valueAt(i).waitDisplayEvent(DISPLAY_CHANGED);
326             }
327         }
328 
329         if (mCached) {
330             // The test activity becomes non-cached and should receive the pending display events
331             bringTestActivityTop();
332 
333             for (int i = 0; i < mDisplayCount; i++) {
334                 // The pending DISPLAY_ADDED & DISPLAY_CHANGED should arrive now
335                 mDisplayBundles.valueAt(i).waitDisplayEvent(DISPLAY_ADDED);
336                 mDisplayBundles.valueAt(i).waitDisplayEvent(DISPLAY_CHANGED);
337             }
338         }
339 
340         // Release the virtual displays
341         for (int i = 0; i < mDisplayCount; i++) {
342             mDisplayBundles.valueAt(i).releaseDisplay();
343         }
344 
345         // DISPLAY_REMOVED should arrive now
346         for (int i = 0; i < mDisplayCount; i++) {
347             mDisplayBundles.valueAt(i).waitDisplayEvent(DISPLAY_REMOVED);
348         }
349     }
350 
351     /**
352      * Launch the test activity that would listen to display events
353      */
launchTestActivity()354     private void launchTestActivity() {
355         Intent intent = new Intent(Intent.ACTION_MAIN);
356         intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
357         intent.putExtra(TEST_MESSENGER, mMessenger);
358         intent.putExtra(TEST_DISPLAYS, mDisplayCount);
359         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
360         SystemUtil.runWithShellPermissionIdentity(
361                 () -> {
362                     mContext.startActivity(intent);
363                 },
364                 android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
365         waitLatch(mLatchActivityLaunch);
366     }
367 
368     /**
369      * Bring the test activity back to top
370      */
bringTestActivityTop()371     private void bringTestActivityTop() {
372         Intent intent = new Intent(Intent.ACTION_MAIN);
373         intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
374         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
375         SystemUtil.runWithShellPermissionIdentity(
376                 () -> {
377                     mContext.startActivity(intent);
378                 },
379                 android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
380     }
381 
382     /**
383      * Bring the test activity into cached mode by launching another 2 apps
384      */
makeTestActivityCached()385     private void makeTestActivityCached() {
386         // Launch another activity to bring the test activity into background
387         Intent intent = new Intent(Intent.ACTION_MAIN);
388         intent.setClass(mContext, SimpleActivity.class);
389         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
390 
391         // Launch another activity to bring the test activity into cached mode
392         Intent intent2 = new Intent(Intent.ACTION_MAIN);
393         intent2.setClass(mContext, SimpleActivity2.class);
394         intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
395         SystemUtil.runWithShellPermissionIdentity(
396                 () -> {
397                     mInstrumentation.startActivitySync(intent);
398                     mInstrumentation.startActivitySync(intent2);
399                 },
400                 android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
401         waitLatch(mLatchActivityCached);
402     }
403 
404     /**
405      * Create a virtual display
406      *
407      * @param name The name of the new virtual display
408      * @return The new virtual display
409      */
createVirtualDisplay(String name)410     private VirtualDisplay createVirtualDisplay(String name) {
411         return mDisplayManager.createVirtualDisplay(name, WIDTH, HEIGHT, DENSITY_MEDIUM,
412                 null /* surface: as we don't actually draw anything, null is enough */,
413                 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
414                 /* flags: a public virtual display that another app can access */);
415     }
416 
417     /**
418      * Wait for CountDownLatch with timeout
419      */
waitLatch(CountDownLatch latch)420     private void waitLatch(CountDownLatch latch) {
421         try {
422             latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
423         } catch (InterruptedException e) {
424             throw new RuntimeException(e);
425         }
426     }
427 }
428