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