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 int secondaryDisplayId = session.getDisplayId(); 294 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 295 // Secondary display disconnected. 296 session.close(); 297 298 assertNotNull("SecondaryActivity must be started.", secondaryActivity); 299 secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY); 300 secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */); 301 302 primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */); 303 } 304 305 /** 306 * Ensure that a non focused display becomes focused when tapping on a focusable window on 307 * that display. 308 */ 309 @Test testTapFocusableWindow()310 public void testTapFocusableWindow() { 311 assumeTrue(supportsMultiDisplay()); 312 assumeFalse(perDisplayFocusEnabled()); 313 314 PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY); 315 final SecondaryActivity secondaryActivity = 316 createManagedInvisibleDisplaySession().startActivityAndFocus(); 317 318 tapOn(primaryActivity); 319 // Ensure primary activity got focus 320 primaryActivity.waitAndAssertWindowFocusState(true); 321 secondaryActivity.waitAndAssertWindowFocusState(false); 322 } 323 324 /** 325 * Ensure that a non focused display does not become focused when tapping on a non-focusable 326 * window on that display. 327 */ 328 @Test testTapNonFocusableWindow()329 public void testTapNonFocusableWindow() { 330 assumeTrue(supportsMultiDisplay()); 331 assumeFalse(perDisplayFocusEnabled()); 332 333 PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY); 334 final SecondaryActivity secondaryActivity = 335 createManagedInvisibleDisplaySession().startActivityAndFocus(); 336 337 // Tap on a window that can't be focused and ensure that the other window in that 338 // display, primaryActivity's window, doesn't get focus. 339 getInstrumentation().runOnMainSync(() -> { 340 View view = new View(primaryActivity); 341 LayoutParams p = new LayoutParams(); 342 p.flags = LayoutParams.FLAG_NOT_FOCUSABLE; 343 primaryActivity.getWindowManager().addView(view, p); 344 }); 345 getInstrumentation().waitForIdleSync(); 346 347 tapOn(primaryActivity); 348 // Ensure secondary activity still has focus 349 secondaryActivity.waitAndAssertWindowFocusState(true); 350 primaryActivity.waitAndAssertWindowFocusState(false); 351 } 352 353 private static class InputTargetActivity extends FocusableActivity { 354 private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds 355 private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000; 356 private static final long TIMEOUT_NEXT_KEY_EVENT = 1000; 357 358 private final Object mLockPointerCapture = new Object(); 359 private final Object mLockKeyEvent = new Object(); 360 361 @GuardedBy("this") 362 private int mDisplayId = INVALID_DISPLAY; 363 @GuardedBy("mLockPointerCapture") 364 private boolean mHasPointerCapture; 365 @GuardedBy("mLockKeyEvent") 366 private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>(); 367 368 @Override onAttachedToWindow()369 public void onAttachedToWindow() { 370 synchronized (this) { 371 mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId(); 372 notify(); 373 } 374 } 375 376 @Override onMovedToDisplay(int displayId, Configuration config)377 public void onMovedToDisplay(int displayId, Configuration config) { 378 synchronized (this) { 379 mDisplayId = displayId; 380 notify(); 381 } 382 } 383 waitAndAssertDisplayId(int displayId)384 void waitAndAssertDisplayId(int displayId) { 385 synchronized (this) { 386 if (mDisplayId != displayId) { 387 try { 388 wait(TIMEOUT_DISPLAY_CHANGED); 389 } catch (InterruptedException e) { 390 } 391 } 392 assertEquals(getLogTag() + " must be moved to the display.", 393 displayId, mDisplayId); 394 } 395 } 396 397 @Override onPointerCaptureChanged(boolean hasCapture)398 public void onPointerCaptureChanged(boolean hasCapture) { 399 synchronized (mLockPointerCapture) { 400 mHasPointerCapture = hasCapture; 401 mLockPointerCapture.notify(); 402 } 403 } 404 waitAndAssertPointerCaptureState(boolean hasCapture)405 void waitAndAssertPointerCaptureState(boolean hasCapture) { 406 synchronized (mLockPointerCapture) { 407 if (mHasPointerCapture != hasCapture) { 408 try { 409 mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED); 410 } catch (InterruptedException e) { 411 } 412 } 413 assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not") 414 + " have pointer capture.", hasCapture, mHasPointerCapture); 415 } 416 } 417 418 // Should be only called from the main thread. requestPointerCapture()419 void requestPointerCapture() { 420 getWindow().getDecorView().requestPointerCapture(); 421 } 422 423 @Override dispatchKeyEvent(KeyEvent event)424 public boolean dispatchKeyEvent(KeyEvent event) { 425 synchronized (mLockKeyEvent) { 426 mKeyEventList.add(event); 427 mLockKeyEvent.notify(); 428 } 429 return true; 430 } 431 getKeyEventCount()432 int getKeyEventCount() { 433 synchronized (mLockKeyEvent) { 434 return mKeyEventList.size(); 435 } 436 } 437 consumeKeyEvent(int action, int keyCode, int flags)438 private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) { 439 synchronized (mLockKeyEvent) { 440 for (int i = mKeyEventList.size() - 1; i >= 0; i--) { 441 final KeyEvent event = mKeyEventList.get(i); 442 if (event.getAction() == action && event.getKeyCode() == keyCode 443 && (event.getFlags() & flags) == flags) { 444 mKeyEventList.remove(event); 445 return event; 446 } 447 } 448 } 449 return null; 450 } 451 assertAndConsumeKeyEvent(int action, int keyCode, int flags)452 void assertAndConsumeKeyEvent(int action, int keyCode, int flags) { 453 assertNotNull(getLogTag() + " must receive key event " + keyCodeToString(keyCode), 454 consumeKeyEvent(action, keyCode, flags)); 455 } 456 waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)457 void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags) { 458 if (consumeKeyEvent(action, keyCode, flags) == null) { 459 synchronized (mLockKeyEvent) { 460 try { 461 mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT); 462 } catch (InterruptedException e) { 463 } 464 } 465 assertAndConsumeKeyEvent(action, keyCode, flags); 466 } 467 } 468 } 469 470 public static class PrimaryActivity extends InputTargetActivity { } 471 472 public static class SecondaryActivity extends InputTargetActivity { } 473 474 public static class LosingFocusActivity extends InputTargetActivity { 475 private boolean mChildWindowHasDrawn = false; 476 477 @GuardedBy("this") 478 private boolean mLosesFocusWhenNewFocusIsNotDrawn = false; 479 addChildWindow()480 void addChildWindow() { 481 getWindowManager().addView(new View(this) { 482 @Override 483 protected void onDraw(Canvas canvas) { 484 mChildWindowHasDrawn = true; 485 } 486 }, new LayoutParams()); 487 } 488 489 @Override onWindowFocusChanged(boolean hasFocus)490 public void onWindowFocusChanged(boolean hasFocus) { 491 if (!hasFocus && !mChildWindowHasDrawn) { 492 synchronized (this) { 493 mLosesFocusWhenNewFocusIsNotDrawn = true; 494 } 495 } 496 super.onWindowFocusChanged(hasFocus); 497 } 498 losesFocusWhenNewFocusIsNotDrawn()499 boolean losesFocusWhenNewFocusIsNotDrawn() { 500 synchronized (this) { 501 return mLosesFocusWhenNewFocusIsNotDrawn; 502 } 503 } 504 } 505 506 public static class AutoEngagePointerCaptureActivity extends InputTargetActivity { 507 @Override onWindowFocusChanged(boolean hasFocus)508 public void onWindowFocusChanged(boolean hasFocus) { 509 if (hasFocus) { 510 requestPointerCapture(); 511 } 512 super.onWindowFocusChanged(hasFocus); 513 } 514 } 515 createManagedInvisibleDisplaySession()516 private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() { 517 return mObjectTracker.manage( 518 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext())); 519 } 520 521 /** An untrusted virtual display that won't show on default screen. */ 522 private static class InvisibleVirtualDisplaySession implements AutoCloseable { 523 private static final int WIDTH = 800; 524 private static final int HEIGHT = 480; 525 private static final int DENSITY = 160; 526 527 private final VirtualDisplay mVirtualDisplay; 528 private final ImageReader mReader; 529 private final Display mDisplay; 530 InvisibleVirtualDisplaySession(Context context)531 InvisibleVirtualDisplaySession(Context context) { 532 mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 533 2 /* maxImages */); 534 mVirtualDisplay = context.getSystemService(DisplayManager.class) 535 .createVirtualDisplay(WindowFocusTests.class.getSimpleName(), 536 WIDTH, HEIGHT, DENSITY, mReader.getSurface(), 537 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 538 mDisplay = mVirtualDisplay.getDisplay(); 539 } 540 getDisplayId()541 int getDisplayId() { 542 return mDisplay.getDisplayId(); 543 } 544 startActivityAndFocus()545 SecondaryActivity startActivityAndFocus() { 546 return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */); 547 } 548 549 @Override close()550 public void close() { 551 if (mVirtualDisplay != null) { 552 mVirtualDisplay.release(); 553 } 554 if (mReader != null) { 555 mReader.close(); 556 } 557 } 558 } 559 createManagedSimulatedDisplaySession()560 private SimulatedDisplaySession createManagedSimulatedDisplaySession() { 561 return mObjectTracker.manage(new SimulatedDisplaySession()); 562 } 563 564 private class SimulatedDisplaySession implements AutoCloseable { 565 private final VirtualDisplaySession mVirtualDisplaySession; 566 private final WindowManagerState.DisplayContent mVirtualDisplay; 567 SimulatedDisplaySession()568 SimulatedDisplaySession() { 569 mVirtualDisplaySession = new VirtualDisplaySession(); 570 mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay(); 571 } 572 getDisplayId()573 int getDisplayId() { 574 return mVirtualDisplay.mId; 575 } 576 startActivityAndFocus()577 SecondaryActivity startActivityAndFocus() { 578 return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */); 579 } 580 581 @Override close()582 public void close() { 583 mVirtualDisplaySession.close(); 584 } 585 } 586 startActivityAndFocus(int displayId, boolean hasFocus)587 private static SecondaryActivity startActivityAndFocus(int displayId, boolean hasFocus) { 588 // An untrusted virtual display won't have focus until the display is touched. 589 final SecondaryActivity activity = WindowManagerTestBase.startActivity( 590 SecondaryActivity.class, displayId, hasFocus); 591 tapOn(activity); 592 activity.waitAndAssertWindowFocusState(true); 593 return activity; 594 } 595 } 596