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