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