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 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_PUBLIC; 21 import static android.view.Display.DEFAULT_DISPLAY; 22 import static android.view.Display.INVALID_DISPLAY; 23 import static android.view.KeyEvent.ACTION_DOWN; 24 import static android.view.KeyEvent.ACTION_UP; 25 import static android.view.KeyEvent.FLAG_CANCELED; 26 import static android.view.KeyEvent.KEYCODE_0; 27 import static android.view.KeyEvent.KEYCODE_1; 28 import static android.view.KeyEvent.KEYCODE_2; 29 import static android.view.KeyEvent.KEYCODE_3; 30 import static android.view.KeyEvent.KEYCODE_4; 31 import static android.view.KeyEvent.KEYCODE_5; 32 import static android.view.KeyEvent.KEYCODE_6; 33 import static android.view.KeyEvent.KEYCODE_7; 34 import static android.view.KeyEvent.KEYCODE_8; 35 import static android.view.KeyEvent.keyCodeToString; 36 37 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 38 39 import static org.junit.Assert.assertEquals; 40 import static org.junit.Assert.assertFalse; 41 import static org.junit.Assert.assertNotNull; 42 import static org.junit.Assume.assumeFalse; 43 import static org.junit.Assume.assumeTrue; 44 45 import android.app.Activity; 46 import android.content.Context; 47 import android.content.res.Configuration; 48 import android.graphics.Canvas; 49 import android.graphics.PixelFormat; 50 import android.graphics.Point; 51 import android.hardware.display.DisplayManager; 52 import android.hardware.display.VirtualDisplay; 53 import android.media.ImageReader; 54 import android.os.SystemClock; 55 import android.platform.test.annotations.Presubmit; 56 import android.view.Display; 57 import android.view.KeyEvent; 58 import android.view.MotionEvent; 59 import android.view.View; 60 import android.view.WindowManager.LayoutParams; 61 62 import androidx.annotation.NonNull; 63 64 import org.junit.Test; 65 66 import java.util.ArrayList; 67 68 import javax.annotation.concurrent.GuardedBy; 69 70 /** 71 * Ensure window focus assignment is executed as expected. 72 * 73 * Build/Install/Run: 74 * atest WindowFocusTests 75 */ 76 @Presubmit 77 public class WindowFocusTests extends WindowManagerTestBase { 78 sendKey(int action, int keyCode, int displayId)79 private static void sendKey(int action, int keyCode, int displayId) { 80 final KeyEvent keyEvent = new KeyEvent(action, keyCode); 81 keyEvent.setDisplayId(displayId); 82 getInstrumentation().sendKeySync(keyEvent); 83 } 84 sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode, int targetDisplayId)85 private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode, 86 int targetDisplayId) { 87 sendAndAssertTargetConsumedKey(target, ACTION_DOWN, keyCode, targetDisplayId); 88 sendAndAssertTargetConsumedKey(target, ACTION_UP, keyCode, targetDisplayId); 89 } 90 sendAndAssertTargetConsumedKey(InputTargetActivity target, int action, int keyCode, int targetDisplayId)91 private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int action, 92 int keyCode, int targetDisplayId) { 93 final int eventCount = target.getKeyEventCount(); 94 sendKey(action, keyCode, targetDisplayId); 95 target.assertAndConsumeKeyEvent(action, keyCode, 0 /* flags */); 96 assertEquals(target.getLogTag() + " must only receive key event sent.", eventCount, 97 target.getKeyEventCount()); 98 } 99 tapOn(@onNull Activity activity)100 private static void tapOn(@NonNull Activity activity) { 101 final Point p = getCenterOfActivityOnScreen(activity); 102 final int displayId = activity.getDisplayId(); 103 104 final long downTime = SystemClock.elapsedRealtime(); 105 final MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, 106 MotionEvent.ACTION_DOWN, p.x, p.y, 0 /* metaState */); 107 downEvent.setDisplayId(displayId); 108 getInstrumentation().sendPointerSync(downEvent); 109 final MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.elapsedRealtime(), 110 MotionEvent.ACTION_UP, p.x, p.y, 0 /* metaState */); 111 upEvent.setDisplayId(displayId); 112 getInstrumentation().sendPointerSync(upEvent); 113 } 114 getCenterOfActivityOnScreen(@onNull Activity activity)115 private static Point getCenterOfActivityOnScreen(@NonNull Activity activity) { 116 final View decorView = activity.getWindow().getDecorView(); 117 final int[] location = new int[2]; 118 decorView.getLocationOnScreen(location); 119 return new Point(location[0] + decorView.getWidth() / 2, 120 location[1] + decorView.getHeight() / 2); 121 } 122 123 /** 124 * Test the following conditions: 125 * - Each display can have a focused window at the same time. 126 * - Focused windows can receive display-specified key events. 127 * - The top focused window can receive display-unspecified key events. 128 * - Taping on a display will make the focused window on it become top-focused. 129 * - The window which lost top-focus can receive display-unspecified cancel events. 130 */ 131 @Test testKeyReceiving()132 public void testKeyReceiving() { 133 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 134 DEFAULT_DISPLAY); 135 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY); 136 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY); 137 138 assumeTrue(supportsMultiDisplay()); 139 140 // VirtualDisplay can't maintain perDisplayFocus because it is not trusted, 141 // so uses SimulatedDisplay instead. 142 final SimulatedDisplaySession session = createManagedSimulatedDisplaySession(); 143 final int secondaryDisplayId = session.getDisplayId(); 144 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 145 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY); 146 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId); 147 148 final boolean perDisplayFocusEnabled = perDisplayFocusEnabled(); 149 if (perDisplayFocusEnabled) { 150 primaryActivity.assertWindowFocusState(true /* hasFocus */); 151 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, DEFAULT_DISPLAY); 152 } else { 153 primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */); 154 } 155 156 // Press display-unspecified keys and a display-specified key but not release them. 157 sendKey(ACTION_DOWN, KEYCODE_5, INVALID_DISPLAY); 158 sendKey(ACTION_DOWN, KEYCODE_6, secondaryDisplayId); 159 sendKey(ACTION_DOWN, KEYCODE_7, INVALID_DISPLAY); 160 secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_5, 0 /* flags */); 161 secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_6, 0 /* flags */); 162 secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_7, 0 /* flags */); 163 164 tapOn(primaryActivity); 165 166 // Assert only display-unspecified key would be cancelled after secondary activity is 167 // not top focused if per-display focus is enabled. Otherwise, assert all non-released 168 // key events sent to secondary activity would be cancelled. 169 secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_5, FLAG_CANCELED); 170 secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_7, FLAG_CANCELED); 171 if (!perDisplayFocusEnabled) { 172 secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED); 173 } 174 assertEquals(secondaryActivity.getLogTag() + " must only receive expected events.", 175 0 /* expected event count */, secondaryActivity.getKeyEventCount()); 176 177 // Assert primary activity become top focused after tapping on default display. 178 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_8, INVALID_DISPLAY); 179 } 180 181 /** 182 * Test if a display targeted by a key event can be moved to top in a single-focus system. 183 */ 184 @Test testMovingDisplayToTopByKeyEvent()185 public void testMovingDisplayToTopByKeyEvent() { 186 assumeTrue(supportsMultiDisplay()); 187 assumeFalse(perDisplayFocusEnabled()); 188 189 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 190 DEFAULT_DISPLAY); 191 final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession(); 192 final int secondaryDisplayId = session.getDisplayId(); 193 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 194 195 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, DEFAULT_DISPLAY); 196 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, INVALID_DISPLAY); 197 198 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, secondaryDisplayId); 199 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, INVALID_DISPLAY); 200 } 201 202 /** 203 * Test if the client is notified about window-focus lost after the new focused window is drawn. 204 */ 205 @Test testDelayLosingFocus()206 public void testDelayLosingFocus() { 207 final LosingFocusActivity activity = startActivity(LosingFocusActivity.class, 208 DEFAULT_DISPLAY); 209 210 getInstrumentation().runOnMainSync(activity::addChildWindow); 211 activity.waitAndAssertWindowFocusState(false /* hasFocus */); 212 assertFalse("Activity must lose window focus after new focused window is drawn.", 213 activity.losesFocusWhenNewFocusIsNotDrawn()); 214 } 215 216 217 /** 218 * Test the following conditions: 219 * - Only the top focused window can have pointer capture. 220 * - The window which lost top-focus can be notified about pointer-capture lost. 221 */ 222 @Test testPointerCapture()223 public void testPointerCapture() { 224 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 225 DEFAULT_DISPLAY); 226 227 // Assert primary activity can have pointer capture before we have multiple focused windows. 228 getInstrumentation().runOnMainSync(primaryActivity::requestPointerCapture); 229 primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 230 231 assumeTrue(supportsMultiDisplay()); 232 final SecondaryActivity secondaryActivity = 233 createManagedInvisibleDisplaySession().startActivityAndFocus(); 234 235 // Assert primary activity lost pointer capture when it is not top focused. 236 primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 237 238 // Assert secondary activity can have pointer capture when it is top focused. 239 getInstrumentation().runOnMainSync(secondaryActivity::requestPointerCapture); 240 secondaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 241 242 tapOn(primaryActivity); 243 primaryActivity.waitAndAssertWindowFocusState(true); 244 245 // Assert secondary activity lost pointer capture when it is not top focused. 246 secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 247 } 248 249 /** 250 * Pointer capture could be requested after activity regains focus. 251 */ 252 @Test testPointerCaptureWhenFocus()253 public void testPointerCaptureWhenFocus() { 254 final AutoEngagePointerCaptureActivity primaryActivity = 255 startActivity(AutoEngagePointerCaptureActivity.class, DEFAULT_DISPLAY); 256 257 // Assert primary activity can have pointer capture before we have multiple focused windows. 258 primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 259 260 assumeTrue(supportsMultiDisplay()); 261 262 // This test only makes sense if `config_perDisplayFocusEnabled` is disabled. 263 assumeFalse(perDisplayFocusEnabled()); 264 265 final SecondaryActivity secondaryActivity = 266 createManagedInvisibleDisplaySession().startActivityAndFocus(); 267 268 primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */); 269 // Assert primary activity lost pointer capture when it is not top focused. 270 primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 271 secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 272 273 tapOn(primaryActivity); 274 primaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */); 275 primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 276 } 277 278 /** 279 * Test if the focused window can still have focus after it is moved to another display. 280 */ 281 @Test testDisplayChanged()282 public void testDisplayChanged() { 283 assumeTrue(supportsMultiDisplay()); 284 285 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 286 DEFAULT_DISPLAY); 287 288 final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession(); 289 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 290 // Secondary display disconnected. 291 session.close(); 292 293 assertNotNull("SecondaryActivity must be started.", secondaryActivity); 294 secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY); 295 secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */); 296 297 primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */); 298 } 299 300 /** 301 * Ensure that a non focused display becomes focused when tapping on a focusable window on 302 * that display. 303 */ 304 @Test testTapFocusableWindow()305 public void testTapFocusableWindow() { 306 assumeTrue(supportsMultiDisplay()); 307 assumeFalse(perDisplayFocusEnabled()); 308 309 PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY); 310 final SecondaryActivity secondaryActivity = 311 createManagedInvisibleDisplaySession().startActivityAndFocus(); 312 313 tapOn(primaryActivity); 314 // Ensure primary activity got focus 315 primaryActivity.waitAndAssertWindowFocusState(true); 316 secondaryActivity.waitAndAssertWindowFocusState(false); 317 } 318 319 /** 320 * Ensure that a non focused display does not become focused when tapping on a non-focusable 321 * window on that display. 322 */ 323 @Test testTapNonFocusableWindow()324 public void testTapNonFocusableWindow() { 325 assumeTrue(supportsMultiDisplay()); 326 assumeFalse(perDisplayFocusEnabled()); 327 328 PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY); 329 final SecondaryActivity secondaryActivity = 330 createManagedInvisibleDisplaySession().startActivityAndFocus(); 331 332 // Tap on a window that can't be focused and ensure that the other window in that 333 // display, primaryActivity's window, doesn't get focus. 334 getInstrumentation().runOnMainSync(() -> { 335 View view = new View(primaryActivity); 336 LayoutParams p = new LayoutParams(); 337 p.flags = LayoutParams.FLAG_NOT_FOCUSABLE; 338 primaryActivity.getWindowManager().addView(view, p); 339 }); 340 getInstrumentation().waitForIdleSync(); 341 342 tapOn(primaryActivity); 343 // Ensure secondary activity still has focus 344 secondaryActivity.waitAndAssertWindowFocusState(true); 345 primaryActivity.waitAndAssertWindowFocusState(false); 346 } 347 348 private static class InputTargetActivity extends FocusableActivity { 349 private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds 350 private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000; 351 private static final long TIMEOUT_NEXT_KEY_EVENT = 1000; 352 353 private final Object mLockPointerCapture = new Object(); 354 private final Object mLockKeyEvent = new Object(); 355 356 @GuardedBy("this") 357 private int mDisplayId = INVALID_DISPLAY; 358 @GuardedBy("mLockPointerCapture") 359 private boolean mHasPointerCapture; 360 @GuardedBy("mLockKeyEvent") 361 private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>(); 362 363 @Override onAttachedToWindow()364 public void onAttachedToWindow() { 365 synchronized (this) { 366 mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId(); 367 notify(); 368 } 369 } 370 371 @Override onMovedToDisplay(int displayId, Configuration config)372 public void onMovedToDisplay(int displayId, Configuration config) { 373 synchronized (this) { 374 mDisplayId = displayId; 375 notify(); 376 } 377 } 378 waitAndAssertDisplayId(int displayId)379 void waitAndAssertDisplayId(int displayId) { 380 synchronized (this) { 381 if (mDisplayId != displayId) { 382 try { 383 wait(TIMEOUT_DISPLAY_CHANGED); 384 } catch (InterruptedException e) { 385 } 386 } 387 assertEquals(getLogTag() + " must be moved to the display.", 388 displayId, mDisplayId); 389 } 390 } 391 392 @Override onPointerCaptureChanged(boolean hasCapture)393 public void onPointerCaptureChanged(boolean hasCapture) { 394 synchronized (mLockPointerCapture) { 395 mHasPointerCapture = hasCapture; 396 mLockPointerCapture.notify(); 397 } 398 } 399 waitAndAssertPointerCaptureState(boolean hasCapture)400 void waitAndAssertPointerCaptureState(boolean hasCapture) { 401 synchronized (mLockPointerCapture) { 402 if (mHasPointerCapture != hasCapture) { 403 try { 404 mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED); 405 } catch (InterruptedException e) { 406 } 407 } 408 assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not") 409 + " have pointer capture.", hasCapture, mHasPointerCapture); 410 } 411 } 412 413 // Should be only called from the main thread. requestPointerCapture()414 void requestPointerCapture() { 415 getWindow().getDecorView().requestPointerCapture(); 416 } 417 418 @Override dispatchKeyEvent(KeyEvent event)419 public boolean dispatchKeyEvent(KeyEvent event) { 420 synchronized (mLockKeyEvent) { 421 mKeyEventList.add(event); 422 mLockKeyEvent.notify(); 423 } 424 return true; 425 } 426 getKeyEventCount()427 int getKeyEventCount() { 428 synchronized (mLockKeyEvent) { 429 return mKeyEventList.size(); 430 } 431 } 432 consumeKeyEvent(int action, int keyCode, int flags)433 private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) { 434 synchronized (mLockKeyEvent) { 435 for (int i = mKeyEventList.size() - 1; i >= 0; i--) { 436 final KeyEvent event = mKeyEventList.get(i); 437 if (event.getAction() == action && event.getKeyCode() == keyCode 438 && (event.getFlags() & flags) == flags) { 439 mKeyEventList.remove(event); 440 return event; 441 } 442 } 443 } 444 return null; 445 } 446 assertAndConsumeKeyEvent(int action, int keyCode, int flags)447 void assertAndConsumeKeyEvent(int action, int keyCode, int flags) { 448 assertNotNull(getLogTag() + " must receive key event " + keyCodeToString(keyCode), 449 consumeKeyEvent(action, keyCode, flags)); 450 } 451 waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)452 void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags) { 453 if (consumeKeyEvent(action, keyCode, flags) == null) { 454 synchronized (mLockKeyEvent) { 455 try { 456 mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT); 457 } catch (InterruptedException e) { 458 } 459 } 460 assertAndConsumeKeyEvent(action, keyCode, flags); 461 } 462 } 463 } 464 465 public static class PrimaryActivity extends InputTargetActivity { } 466 467 public static class SecondaryActivity extends InputTargetActivity { } 468 469 public static class LosingFocusActivity extends InputTargetActivity { 470 private boolean mChildWindowHasDrawn = false; 471 472 @GuardedBy("this") 473 private boolean mLosesFocusWhenNewFocusIsNotDrawn = false; 474 addChildWindow()475 void addChildWindow() { 476 getWindowManager().addView(new View(this) { 477 @Override 478 protected void onDraw(Canvas canvas) { 479 mChildWindowHasDrawn = true; 480 } 481 }, new LayoutParams()); 482 } 483 484 @Override onWindowFocusChanged(boolean hasFocus)485 public void onWindowFocusChanged(boolean hasFocus) { 486 if (!hasFocus && !mChildWindowHasDrawn) { 487 synchronized (this) { 488 mLosesFocusWhenNewFocusIsNotDrawn = true; 489 } 490 } 491 super.onWindowFocusChanged(hasFocus); 492 } 493 losesFocusWhenNewFocusIsNotDrawn()494 boolean losesFocusWhenNewFocusIsNotDrawn() { 495 synchronized (this) { 496 return mLosesFocusWhenNewFocusIsNotDrawn; 497 } 498 } 499 } 500 501 public static class AutoEngagePointerCaptureActivity extends InputTargetActivity { 502 @Override onWindowFocusChanged(boolean hasFocus)503 public void onWindowFocusChanged(boolean hasFocus) { 504 if (hasFocus) { 505 requestPointerCapture(); 506 } 507 super.onWindowFocusChanged(hasFocus); 508 } 509 } 510 createManagedInvisibleDisplaySession()511 private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() { 512 return mObjectTracker.manage( 513 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext())); 514 } 515 516 /** An untrusted virtual display that won't show on default screen. */ 517 private static class InvisibleVirtualDisplaySession implements AutoCloseable { 518 private static final int WIDTH = 800; 519 private static final int HEIGHT = 480; 520 private static final int DENSITY = 160; 521 522 private final VirtualDisplay mVirtualDisplay; 523 private final ImageReader mReader; 524 private final Display mDisplay; 525 InvisibleVirtualDisplaySession(Context context)526 InvisibleVirtualDisplaySession(Context context) { 527 mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 528 2 /* maxImages */); 529 mVirtualDisplay = context.getSystemService(DisplayManager.class) 530 .createVirtualDisplay(WindowFocusTests.class.getSimpleName(), 531 WIDTH, HEIGHT, DENSITY, mReader.getSurface(), 532 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 533 mDisplay = mVirtualDisplay.getDisplay(); 534 } 535 getDisplayId()536 int getDisplayId() { 537 return mDisplay.getDisplayId(); 538 } 539 startActivityAndFocus()540 SecondaryActivity startActivityAndFocus() { 541 return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */); 542 } 543 544 @Override close()545 public void close() { 546 if (mVirtualDisplay != null) { 547 mVirtualDisplay.release(); 548 } 549 if (mReader != null) { 550 mReader.close(); 551 } 552 } 553 } 554 createManagedSimulatedDisplaySession()555 private SimulatedDisplaySession createManagedSimulatedDisplaySession() { 556 return mObjectTracker.manage(new SimulatedDisplaySession()); 557 } 558 559 private class SimulatedDisplaySession implements AutoCloseable { 560 private final VirtualDisplaySession mVirtualDisplaySession; 561 private final WindowManagerState.DisplayContent mVirtualDisplay; 562 SimulatedDisplaySession()563 SimulatedDisplaySession() { 564 mVirtualDisplaySession = new VirtualDisplaySession(); 565 mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay(); 566 } 567 getDisplayId()568 int getDisplayId() { 569 return mVirtualDisplay.mId; 570 } 571 startActivityAndFocus()572 SecondaryActivity startActivityAndFocus() { 573 return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */); 574 } 575 576 @Override close()577 public void close() { 578 mVirtualDisplaySession.close(); 579 } 580 } 581 startActivityAndFocus(int displayId, boolean hasFocus)582 private static SecondaryActivity startActivityAndFocus(int displayId, boolean hasFocus) { 583 // An untrusted virtual display won't have focus until the display is touched. 584 final SecondaryActivity activity = WindowManagerTestBase.startActivity( 585 SecondaryActivity.class, displayId, hasFocus); 586 tapOn(activity); 587 activity.waitAndAssertWindowFocusState(true); 588 return activity; 589 } 590 } 591