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