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