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_OWN_FOCUS; 21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; 22 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED; 23 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 import static android.view.Display.INVALID_DISPLAY; 26 import static android.view.KeyEvent.ACTION_DOWN; 27 import static android.view.KeyEvent.ACTION_UP; 28 import static android.view.KeyEvent.FLAG_CANCELED; 29 import static android.view.KeyEvent.KEYCODE_0; 30 import static android.view.KeyEvent.KEYCODE_1; 31 import static android.view.KeyEvent.KEYCODE_2; 32 import static android.view.KeyEvent.KEYCODE_3; 33 import static android.view.KeyEvent.KEYCODE_4; 34 import static android.view.KeyEvent.KEYCODE_5; 35 import static android.view.KeyEvent.KEYCODE_6; 36 import static android.view.KeyEvent.KEYCODE_7; 37 import static android.view.KeyEvent.KEYCODE_8; 38 import static android.view.KeyEvent.KEYCODE_9; 39 import static android.view.KeyEvent.keyCodeToString; 40 41 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 42 43 import static org.junit.Assert.assertEquals; 44 import static org.junit.Assert.assertFalse; 45 import static org.junit.Assert.assertNotNull; 46 import static org.junit.Assume.assumeFalse; 47 import static org.junit.Assume.assumeTrue; 48 49 import android.app.Activity; 50 import android.content.Context; 51 import android.content.res.Configuration; 52 import android.graphics.Canvas; 53 import android.graphics.PixelFormat; 54 import android.graphics.Point; 55 import android.hardware.display.DisplayManager; 56 import android.hardware.display.VirtualDisplay; 57 import android.media.ImageReader; 58 import android.os.SystemClock; 59 import android.platform.test.annotations.Presubmit; 60 import android.view.Display; 61 import android.view.KeyEvent; 62 import android.view.MotionEvent; 63 import android.view.View; 64 import android.view.WindowManager.LayoutParams; 65 66 import androidx.annotation.NonNull; 67 68 import com.android.compatibility.common.util.SystemUtil; 69 70 import org.junit.Test; 71 72 import java.util.ArrayList; 73 74 import javax.annotation.concurrent.GuardedBy; 75 76 /** 77 * Ensure window focus assignment is executed as expected. 78 * 79 * Build/Install/Run: 80 * atest WindowFocusTests 81 */ 82 @Presubmit 83 public class WindowFocusTests extends WindowManagerTestBase { 84 sendKey(int action, int keyCode, int displayId)85 private static void sendKey(int action, int keyCode, int displayId) { 86 final KeyEvent keyEvent = new KeyEvent(action, keyCode); 87 keyEvent.setDisplayId(displayId); 88 getInstrumentation().sendKeySync(keyEvent); 89 } 90 sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode, int targetDisplayId)91 private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode, 92 int targetDisplayId) { 93 sendAndAssertTargetConsumedKey(target, ACTION_DOWN, keyCode, targetDisplayId); 94 sendAndAssertTargetConsumedKey(target, ACTION_UP, keyCode, targetDisplayId); 95 } 96 sendAndAssertTargetConsumedKey(InputTargetActivity target, int action, int keyCode, int targetDisplayId)97 private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int action, 98 int keyCode, int targetDisplayId) { 99 final int eventCount = target.getKeyEventCount(); 100 sendKey(action, keyCode, targetDisplayId); 101 target.assertAndConsumeKeyEvent(action, keyCode, 0 /* flags */); 102 assertEquals(target.getLogTag() + " must only receive key event sent.", eventCount, 103 target.getKeyEventCount()); 104 } 105 tapOn(@onNull Activity activity)106 private static void tapOn(@NonNull Activity activity) { 107 final Point p = getCenterOfActivityOnScreen(activity); 108 final int displayId = activity.getDisplayId(); 109 110 final long downTime = SystemClock.elapsedRealtime(); 111 final MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, 112 MotionEvent.ACTION_DOWN, p.x, p.y, 0 /* metaState */); 113 downEvent.setDisplayId(displayId); 114 getInstrumentation().sendPointerSync(downEvent); 115 final MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.elapsedRealtime(), 116 MotionEvent.ACTION_UP, p.x, p.y, 0 /* metaState */); 117 upEvent.setDisplayId(displayId); 118 getInstrumentation().sendPointerSync(upEvent); 119 } 120 getCenterOfActivityOnScreen(@onNull Activity activity)121 private static Point getCenterOfActivityOnScreen(@NonNull Activity activity) { 122 final View decorView = activity.getWindow().getDecorView(); 123 final int[] location = new int[2]; 124 decorView.getLocationOnScreen(location); 125 return new Point(location[0] + decorView.getWidth() / 2, 126 location[1] + decorView.getHeight() / 2); 127 } 128 129 /** 130 * Test the following conditions: 131 * - Each display can have a focused window at the same time. 132 * - Focused windows can receive display-specified key events. 133 * - The top focused window can receive display-unspecified key events. 134 * - Taping on a display will make the focused window on it become top-focused. 135 * - The window which lost top-focus can receive display-unspecified cancel events. 136 */ 137 @Test testKeyReceiving()138 public void testKeyReceiving() { 139 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 140 DEFAULT_DISPLAY); 141 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY); 142 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY); 143 144 assumeTrue(supportsMultiDisplay()); 145 146 // VirtualDisplay can't maintain perDisplayFocus because it is not trusted, 147 // so uses SimulatedDisplay instead. 148 final SimulatedDisplaySession session = createManagedSimulatedDisplaySession(); 149 final int secondaryDisplayId = session.getDisplayId(); 150 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 151 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY); 152 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId); 153 154 // After launching the second activity the primary activities focus depends on the state of 155 // perDisplayFocusEnabled. If the display has its own focus, then the activities still has 156 // window focus. If it is disabled, then primary activity should no longer have window focus 157 // because the secondary activity got it. 158 primaryActivity.waitAndAssertWindowFocusState(perDisplayFocusEnabled()); 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 @Test testKeyReceivingWithDisplayWithOwnFocus()186 public void testKeyReceivingWithDisplayWithOwnFocus() { 187 assumeTrue(supportsMultiDisplay()); 188 // This test specifically tests the behavior if a single display manages its own focus. 189 // Key receiving with perDisplayFocusEnabled is handled in #testKeyReceiving() 190 assumeFalse(perDisplayFocusEnabled()); 191 192 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 193 DEFAULT_DISPLAY); 194 195 final VirtualDisplayWithOwnFocusSession session = 196 createManagedVirtualDisplayWithOwnFocusSession(); 197 final int secondaryDisplayId = session.getDisplayId(); 198 final SecondaryActivity secondaryActivity = session.startActivityAndFocus( 199 SecondaryActivity.class); 200 201 // The secondary display and activity gained focus; the window on default display 202 // has no longer focus because the secondary display is also the top display. 203 primaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ false); 204 secondaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ true); 205 206 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_0, INVALID_DISPLAY); 207 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_1, secondaryDisplayId); 208 209 // Send a key event to the primary activity on the default display to make it the top 210 // focused display.; the secondary ones did not lose window focus. 211 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_6, DEFAULT_DISPLAY); 212 primaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ true); 213 secondaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ true); 214 215 // Assert primary activity become top focused after sending targeted key to default display 216 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_8, INVALID_DISPLAY); 217 // And targeted keys to the secondary display should still arrive at the secondary 218 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_9, secondaryDisplayId); 219 220 assertEquals(secondaryActivity.getLogTag() + " must only receive expected events", 221 0 /* expected event count */, secondaryActivity.getKeyEventCount()); 222 } 223 224 /** 225 * Test the {@link Display#FLAG_OWN_FOCUS} behavior. 226 * The flag is similar to {@link #perDisplayFocusEnabled()} but instead of affecting all 227 * displays it only affects the displays that have the flag set. 228 */ 229 @Test testOwnFocus()230 public void testOwnFocus() { 231 assumeTrue(supportsMultiDisplay()); 232 233 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 234 DEFAULT_DISPLAY); 235 236 // Create two VirtualDisplays with its own focus and launch an activity on them 237 final VirtualDisplayWithOwnFocusSession secondarySession = 238 createManagedVirtualDisplayWithOwnFocusSession(); 239 final SecondaryActivity secondaryActivity = secondarySession.startActivityAndFocus( 240 SecondaryActivity.class); 241 final VirtualDisplayWithOwnFocusSession tertiarySession = 242 createManagedVirtualDisplayWithOwnFocusSession(); 243 final TertiaryActivity tertiaryActivity = tertiarySession.startActivityAndFocus( 244 TertiaryActivity.class); 245 246 // The primary activity will have window focus based on perDisplayFocusEnabled. If it is 247 // enabled then all displays have their own focus. The primary activity should have focus. 248 // If it is disabled then it should have lost the focus when the secondary activity launched 249 // on the second monitor. That brought that display to the top and removed window focus from 250 // the default display (where primary activity is running). 251 primaryActivity.waitAndAssertWindowFocusState(perDisplayFocusEnabled()); 252 253 // Both activities running on displays with their own focus should have window focus. 254 secondaryActivity.waitAndAssertWindowFocusState(true); 255 tertiaryActivity.waitAndAssertWindowFocusState(true); 256 257 // Making the primary activity the top focus (by tapping it) will make 258 // it focused. The other two displays still have a focused window 259 tapOn(primaryActivity); 260 primaryActivity.waitAndAssertWindowFocusState(true); 261 secondaryActivity.waitAndAssertWindowFocusState(true); 262 tertiaryActivity.waitAndAssertWindowFocusState(true); 263 } 264 265 /** 266 * Test if a display targeted by a key event can be moved to top in a single-focus system. 267 */ 268 @Test testMovingDisplayToTopByKeyEvent()269 public void testMovingDisplayToTopByKeyEvent() { 270 assumeTrue(supportsMultiDisplay()); 271 272 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 273 DEFAULT_DISPLAY); 274 final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession(); 275 final int secondaryDisplayId = session.getDisplayId(); 276 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 277 278 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, DEFAULT_DISPLAY); 279 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, INVALID_DISPLAY); 280 281 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, secondaryDisplayId); 282 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, INVALID_DISPLAY); 283 } 284 285 /** 286 * The display flag FLAG_STEAL_TOP_FOCUS_DISABLED prevents a display from stealing the top 287 * focus from another display. Sending targeted key events to a display usually raises that 288 * display to be the top focused display if it is not yet. If the FLAG_STEAL_TOP_FOCUS_DISABLED 289 * is set then that should not happen and the previous display stays the top focused display. 290 */ 291 @Test testStealingTopFocusDisabledDoesNotMoveDisplayToTop()292 public void testStealingTopFocusDisabledDoesNotMoveDisplayToTop() { 293 assumeTrue(supportsMultiDisplay()); 294 295 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 296 DEFAULT_DISPLAY); 297 // Primary should have window focus for sure after launching 298 primaryActivity.waitAndAssertWindowFocusState(/* hasFocus */ true); 299 // Confirm this display has the top focus and receives untargeted events 300 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY); 301 // Confirm this display has the top focus and receives targeted events 302 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY); 303 304 // Create a VirtualDisplay with top focus disabled and launch an activity on it 305 final VirtualDisplayWithOwnFocusSession session = 306 createManagedVirtualDisplayWithOwnFocusSession( 307 VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED); 308 final int secondaryDisplayId = session.getDisplayId(); 309 // Launching the activity on the secondary display will give it window focus. 310 final SecondaryActivity secondaryActivity = session.startActivityAndFocus( 311 SecondaryActivity.class); 312 313 // Primary should have window focus because it still is top focused display 314 // Secondary should have window focus because it manages its own focus 315 primaryActivity.waitAndAssertWindowFocusState(/* hasFocus */ true); 316 secondaryActivity.waitAndAssertWindowFocusState(/* hasFocus */ true); 317 318 // Confirm the default display still has top display focus 319 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_2, INVALID_DISPLAY); 320 321 // Send a targeted key event to the secondary display. 322 // The secondary display should not get top focus because of FLAG_STEAL_TOP_FOCUS_DISABLED 323 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId); 324 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, INVALID_DISPLAY); 325 326 // Now also check a tap does also not raise the top focus to the secondary display 327 tapOn(secondaryActivity); 328 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_5, INVALID_DISPLAY); 329 330 // Tap the default display and check that the secondary display still has a window focus 331 tapOn(primaryActivity); 332 secondaryActivity.waitAndAssertWindowFocusState(/*hasFocus*/ true); 333 sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_6, INVALID_DISPLAY); 334 sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_7, secondaryDisplayId); 335 } 336 337 /** 338 * Test if the client is notified about window-focus lost after the new focused window is drawn. 339 */ 340 @Test testDelayLosingFocus()341 public void testDelayLosingFocus() { 342 final LosingFocusActivity activity = startActivity(LosingFocusActivity.class, 343 DEFAULT_DISPLAY); 344 345 getInstrumentation().runOnMainSync(activity::addChildWindow); 346 activity.waitAndAssertWindowFocusState(false /* hasFocus */); 347 assertFalse("Activity must lose window focus after new focused window is drawn.", 348 activity.losesFocusWhenNewFocusIsNotDrawn()); 349 } 350 351 352 /** 353 * Test the following conditions: 354 * - Only the top focused window can have pointer capture. 355 * - The window which lost top-focus can be notified about pointer-capture lost. 356 */ 357 @Test testPointerCapture()358 public void testPointerCapture() { 359 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 360 DEFAULT_DISPLAY); 361 362 // Assert primary activity can have pointer capture before we have multiple focused windows. 363 getInstrumentation().runOnMainSync(primaryActivity::requestPointerCapture); 364 primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 365 366 assumeTrue(supportsMultiDisplay()); 367 final SecondaryActivity secondaryActivity = 368 createManagedInvisibleDisplaySession().startActivityAndFocus(); 369 370 // Assert primary activity lost pointer capture when it is not top focused. 371 primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 372 373 // Assert secondary activity can have pointer capture when it is top focused. 374 getInstrumentation().runOnMainSync(secondaryActivity::requestPointerCapture); 375 secondaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 376 377 tapOn(primaryActivity); 378 primaryActivity.waitAndAssertWindowFocusState(true); 379 380 // Assert secondary activity lost pointer capture when it is not top focused. 381 secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 382 } 383 384 /** 385 * Pointer capture could be requested after activity regains focus. 386 */ 387 @Test testPointerCaptureWhenFocus()388 public void testPointerCaptureWhenFocus() { 389 final AutoEngagePointerCaptureActivity primaryActivity = 390 startActivity(AutoEngagePointerCaptureActivity.class, DEFAULT_DISPLAY); 391 392 // Assert primary activity can have pointer capture before we have multiple focused windows. 393 primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 394 395 assumeTrue(supportsMultiDisplay()); 396 397 // This test only makes sense if `config_perDisplayFocusEnabled` is disabled. 398 assumeFalse(perDisplayFocusEnabled()); 399 400 final SecondaryActivity secondaryActivity = 401 createManagedInvisibleDisplaySession().startActivityAndFocus(); 402 403 primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */); 404 // Assert primary activity lost pointer capture when it is not top focused. 405 primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 406 secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */); 407 408 tapOn(primaryActivity); 409 primaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */); 410 primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */); 411 } 412 413 /** 414 * Test if the focused window can still have focus after it is moved to another display. 415 */ 416 @Test testDisplayChanged()417 public void testDisplayChanged() { 418 assumeTrue(supportsMultiDisplay()); 419 420 final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, 421 DEFAULT_DISPLAY); 422 423 final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession(); 424 final SecondaryActivity secondaryActivity = session.startActivityAndFocus(); 425 // Secondary display disconnected. 426 session.close(); 427 428 assertNotNull("SecondaryActivity must be started.", secondaryActivity); 429 secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY); 430 secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */); 431 432 primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */); 433 } 434 435 /** 436 * Ensure that a non focused display becomes focused when tapping on a focusable window on 437 * that display. 438 */ 439 @Test testTapFocusableWindow()440 public void testTapFocusableWindow() { 441 assumeTrue(supportsMultiDisplay()); 442 assumeFalse(perDisplayFocusEnabled()); 443 444 PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY); 445 final SecondaryActivity secondaryActivity = 446 createManagedInvisibleDisplaySession().startActivityAndFocus(); 447 448 tapOn(primaryActivity); 449 // Ensure primary activity got focus 450 primaryActivity.waitAndAssertWindowFocusState(true); 451 secondaryActivity.waitAndAssertWindowFocusState(false); 452 } 453 454 /** 455 * Ensure that a non focused display does not become focused when tapping on a non-focusable 456 * window on that display. 457 */ 458 @Test testTapNonFocusableWindow()459 public void testTapNonFocusableWindow() { 460 assumeTrue(supportsMultiDisplay()); 461 assumeFalse(perDisplayFocusEnabled()); 462 463 PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY); 464 final SecondaryActivity secondaryActivity = 465 createManagedInvisibleDisplaySession().startActivityAndFocus(); 466 467 // Tap on a window that can't be focused and ensure that the other window in that 468 // display, primaryActivity's window, doesn't get focus. 469 getInstrumentation().runOnMainSync(() -> { 470 View view = new View(primaryActivity); 471 LayoutParams p = new LayoutParams(); 472 p.flags = LayoutParams.FLAG_NOT_FOCUSABLE; 473 primaryActivity.getWindowManager().addView(view, p); 474 }); 475 getInstrumentation().waitForIdleSync(); 476 477 tapOn(primaryActivity); 478 // Ensure secondary activity still has focus 479 secondaryActivity.waitAndAssertWindowFocusState(true); 480 primaryActivity.waitAndAssertWindowFocusState(false); 481 } 482 483 private static class InputTargetActivity extends FocusableActivity { 484 private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds 485 private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000; 486 private static final long TIMEOUT_NEXT_KEY_EVENT = 1000; 487 488 private final Object mLockPointerCapture = new Object(); 489 private final Object mLockKeyEvent = new Object(); 490 491 @GuardedBy("this") 492 private int mDisplayId = INVALID_DISPLAY; 493 @GuardedBy("mLockPointerCapture") 494 private boolean mHasPointerCapture; 495 @GuardedBy("mLockKeyEvent") 496 private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>(); 497 498 @Override onAttachedToWindow()499 public void onAttachedToWindow() { 500 synchronized (this) { 501 mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId(); 502 notify(); 503 } 504 } 505 506 @Override onMovedToDisplay(int displayId, Configuration config)507 public void onMovedToDisplay(int displayId, Configuration config) { 508 synchronized (this) { 509 mDisplayId = displayId; 510 notify(); 511 } 512 } 513 waitAndAssertDisplayId(int displayId)514 void waitAndAssertDisplayId(int displayId) { 515 synchronized (this) { 516 if (mDisplayId != displayId) { 517 try { 518 wait(TIMEOUT_DISPLAY_CHANGED); 519 } catch (InterruptedException e) { 520 } 521 } 522 assertEquals(getLogTag() + " must be moved to the display.", 523 displayId, mDisplayId); 524 } 525 } 526 527 @Override onPointerCaptureChanged(boolean hasCapture)528 public void onPointerCaptureChanged(boolean hasCapture) { 529 synchronized (mLockPointerCapture) { 530 mHasPointerCapture = hasCapture; 531 mLockPointerCapture.notify(); 532 } 533 } 534 waitAndAssertPointerCaptureState(boolean hasCapture)535 void waitAndAssertPointerCaptureState(boolean hasCapture) { 536 synchronized (mLockPointerCapture) { 537 if (mHasPointerCapture != hasCapture) { 538 try { 539 mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED); 540 } catch (InterruptedException e) { 541 } 542 } 543 assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not") 544 + " have pointer capture.", hasCapture, mHasPointerCapture); 545 } 546 } 547 548 // Should be only called from the main thread. requestPointerCapture()549 void requestPointerCapture() { 550 getWindow().getDecorView().requestPointerCapture(); 551 } 552 553 @Override dispatchKeyEvent(KeyEvent event)554 public boolean dispatchKeyEvent(KeyEvent event) { 555 synchronized (mLockKeyEvent) { 556 mKeyEventList.add(event); 557 mLockKeyEvent.notify(); 558 } 559 return true; 560 } 561 getKeyEventCount()562 int getKeyEventCount() { 563 synchronized (mLockKeyEvent) { 564 return mKeyEventList.size(); 565 } 566 } 567 consumeKeyEvent(int action, int keyCode, int flags)568 private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) { 569 synchronized (mLockKeyEvent) { 570 for (int i = mKeyEventList.size() - 1; i >= 0; i--) { 571 final KeyEvent event = mKeyEventList.get(i); 572 if (event.getAction() == action && event.getKeyCode() == keyCode 573 && (event.getFlags() & flags) == flags) { 574 mKeyEventList.remove(event); 575 return event; 576 } 577 } 578 } 579 return null; 580 } 581 assertAndConsumeKeyEvent(int action, int keyCode, int flags)582 void assertAndConsumeKeyEvent(int action, int keyCode, int flags) { 583 assertNotNull(getLogTag() + " must receive key event " + keyCodeToString(keyCode), 584 consumeKeyEvent(action, keyCode, flags)); 585 } 586 waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)587 void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags) { 588 if (consumeKeyEvent(action, keyCode, flags) == null) { 589 synchronized (mLockKeyEvent) { 590 try { 591 mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT); 592 } catch (InterruptedException e) { 593 } 594 } 595 assertAndConsumeKeyEvent(action, keyCode, flags); 596 } 597 } 598 } 599 600 public static class PrimaryActivity extends InputTargetActivity { } 601 602 public static class SecondaryActivity extends InputTargetActivity { } 603 604 public static class TertiaryActivity extends InputTargetActivity { } 605 606 public static class LosingFocusActivity extends InputTargetActivity { 607 private boolean mChildWindowHasDrawn = false; 608 609 @GuardedBy("this") 610 private boolean mLosesFocusWhenNewFocusIsNotDrawn = false; 611 addChildWindow()612 void addChildWindow() { 613 getWindowManager().addView(new View(this) { 614 @Override 615 protected void onDraw(Canvas canvas) { 616 mChildWindowHasDrawn = true; 617 } 618 }, new LayoutParams()); 619 } 620 621 @Override onWindowFocusChanged(boolean hasFocus)622 public void onWindowFocusChanged(boolean hasFocus) { 623 if (!hasFocus && !mChildWindowHasDrawn) { 624 synchronized (this) { 625 mLosesFocusWhenNewFocusIsNotDrawn = true; 626 } 627 } 628 super.onWindowFocusChanged(hasFocus); 629 } 630 losesFocusWhenNewFocusIsNotDrawn()631 boolean losesFocusWhenNewFocusIsNotDrawn() { 632 synchronized (this) { 633 return mLosesFocusWhenNewFocusIsNotDrawn; 634 } 635 } 636 } 637 638 public static class AutoEngagePointerCaptureActivity extends InputTargetActivity { 639 @Override onWindowFocusChanged(boolean hasFocus)640 public void onWindowFocusChanged(boolean hasFocus) { 641 if (hasFocus) { 642 requestPointerCapture(); 643 } 644 super.onWindowFocusChanged(hasFocus); 645 } 646 } 647 createManagedInvisibleDisplaySession()648 private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() { 649 return mObjectTracker.manage( 650 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext())); 651 } 652 653 /** An untrusted virtual display that won't show on default screen. */ 654 private static class InvisibleVirtualDisplaySession implements AutoCloseable { 655 private static final int WIDTH = 800; 656 private static final int HEIGHT = 480; 657 private static final int DENSITY = 160; 658 659 private final VirtualDisplay mVirtualDisplay; 660 private final ImageReader mReader; 661 private final Display mDisplay; 662 InvisibleVirtualDisplaySession(Context context)663 InvisibleVirtualDisplaySession(Context context) { 664 mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 665 2 /* maxImages */); 666 mVirtualDisplay = context.getSystemService(DisplayManager.class) 667 .createVirtualDisplay(WindowFocusTests.class.getSimpleName(), 668 WIDTH, HEIGHT, DENSITY, mReader.getSurface(), 669 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 670 mDisplay = mVirtualDisplay.getDisplay(); 671 } 672 getDisplayId()673 int getDisplayId() { 674 return mDisplay.getDisplayId(); 675 } 676 startActivityAndFocus()677 SecondaryActivity startActivityAndFocus() { 678 return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */, 679 SecondaryActivity.class); 680 } 681 682 @Override close()683 public void close() { 684 if (mVirtualDisplay != null) { 685 mVirtualDisplay.release(); 686 } 687 if (mReader != null) { 688 mReader.close(); 689 } 690 } 691 } 692 createManagedVirtualDisplayWithOwnFocusSession()693 private VirtualDisplayWithOwnFocusSession createManagedVirtualDisplayWithOwnFocusSession() { 694 return createManagedVirtualDisplayWithOwnFocusSession(/* additionalFlags= */ 0); 695 } 696 createManagedVirtualDisplayWithOwnFocusSession( int additionalFlags)697 private VirtualDisplayWithOwnFocusSession createManagedVirtualDisplayWithOwnFocusSession( 698 int additionalFlags) { 699 return mObjectTracker.manage( 700 new VirtualDisplayWithOwnFocusSession(getInstrumentation().getTargetContext(), 701 additionalFlags)); 702 } 703 704 /** A trusted virtual display that has its own focus and touch mode states. */ 705 private static class VirtualDisplayWithOwnFocusSession implements AutoCloseable { 706 private static final int WIDTH = 800; 707 private static final int HEIGHT = 480; 708 private static final int DENSITY = 160; 709 710 private VirtualDisplay mVirtualDisplay; 711 private final ImageReader mReader; 712 private final Display mDisplay; 713 714 /** 715 * @param context The context, used to get the DisplayManager. 716 * @param additionalFlags Additional VirtualDisplayFlag to add. See 717 * {@link #getVirtualDisplayFlags()} for the default flags that are 718 * set. 719 */ VirtualDisplayWithOwnFocusSession(Context context, int additionalFlags)720 VirtualDisplayWithOwnFocusSession(Context context, int additionalFlags) { 721 mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 722 /* maxImages= */ 2); 723 SystemUtil.runWithShellPermissionIdentity(() -> { 724 mVirtualDisplay = context.getSystemService(DisplayManager.class) 725 .createVirtualDisplay(WindowFocusTests.class.getSimpleName(), WIDTH, HEIGHT, 726 DENSITY, mReader.getSurface(), 727 getVirtualDisplayFlags() | additionalFlags); 728 }); 729 mDisplay = mVirtualDisplay.getDisplay(); 730 } 731 732 /** 733 * @return Get the default VirtualDisplayFlags to set for the creation of the VirtualDisplay 734 */ getVirtualDisplayFlags()735 int getVirtualDisplayFlags() { 736 return VIRTUAL_DISPLAY_FLAG_PUBLIC 737 | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY 738 | VIRTUAL_DISPLAY_FLAG_TRUSTED 739 | VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; 740 } 741 getDisplayId()742 int getDisplayId() { 743 return mDisplay.getDisplayId(); 744 } 745 startActivityAndFocus(Class<T> cls)746 <T extends InputTargetActivity> T startActivityAndFocus(Class<T> cls) { 747 return WindowFocusTests.startActivityAndFocus(getDisplayId(), /* hasFocus= */ true, 748 cls); 749 } 750 751 @Override close()752 public void close() { 753 if (mVirtualDisplay != null) { 754 mVirtualDisplay.release(); 755 } 756 if (mReader != null) { 757 mReader.close(); 758 } 759 } 760 } 761 createManagedSimulatedDisplaySession()762 private SimulatedDisplaySession createManagedSimulatedDisplaySession() { 763 return mObjectTracker.manage(new SimulatedDisplaySession()); 764 } 765 766 private class SimulatedDisplaySession implements AutoCloseable { 767 private final VirtualDisplaySession mVirtualDisplaySession; 768 private final WindowManagerState.DisplayContent mVirtualDisplay; 769 SimulatedDisplaySession()770 SimulatedDisplaySession() { 771 mVirtualDisplaySession = new VirtualDisplaySession(); 772 mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay(); 773 } 774 getDisplayId()775 int getDisplayId() { 776 return mVirtualDisplay.mId; 777 } 778 startActivityAndFocus()779 SecondaryActivity startActivityAndFocus() { 780 return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */, 781 SecondaryActivity.class); 782 } 783 784 @Override close()785 public void close() { 786 mVirtualDisplaySession.close(); 787 } 788 } 789 startActivityAndFocus(int displayId, boolean hasFocus, Class<T> cls)790 private static <T extends InputTargetActivity> T startActivityAndFocus(int displayId, 791 boolean hasFocus, Class<T> cls) { 792 // An untrusted virtual display won't have focus until the display is touched. 793 final T activity = WindowManagerTestBase.startActivity( 794 cls, displayId, hasFocus); 795 tapOn(activity); 796 activity.waitAndAssertWindowFocusState(true); 797 return activity; 798 } 799 } 800