1 /* 2 * Copyright (C) 2019 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.server.wm.ActivityManagerTestBase.launchHomeActivityNoWait; 20 import static android.server.wm.BarTestUtils.assumeHasStatusBar; 21 import static android.server.wm.UiDeviceUtils.pressUnlockButton; 22 import static android.server.wm.UiDeviceUtils.pressWakeupButton; 23 import static android.server.wm.WindowUntrustedTouchTest.MIN_POSITIVE_OPACITY; 24 import static android.server.wm.app.Components.OverlayTestService.EXTRA_LAYOUT_PARAMS; 25 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 26 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 27 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 28 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 29 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 30 31 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 32 33 import static junit.framework.Assert.assertFalse; 34 import static junit.framework.Assert.assertTrue; 35 36 import static org.junit.Assert.assertEquals; 37 import static org.junit.Assert.fail; 38 39 import android.app.Activity; 40 import android.app.Instrumentation; 41 import android.content.ContentResolver; 42 import android.content.Intent; 43 import android.graphics.Color; 44 import android.graphics.Point; 45 import android.graphics.Rect; 46 import android.hardware.input.InputManager; 47 import android.os.Bundle; 48 import android.os.SystemClock; 49 import android.platform.test.annotations.Presubmit; 50 import android.provider.Settings; 51 import android.server.wm.WindowManagerState.WindowState; 52 import android.server.wm.app.Components; 53 import android.server.wm.settings.SettingsSession; 54 import android.util.ArraySet; 55 import android.util.Log; 56 import android.view.Gravity; 57 import android.view.InputDevice; 58 import android.view.MotionEvent; 59 import android.view.View; 60 import android.view.WindowInsets; 61 import android.view.WindowManager; 62 import android.view.WindowMetrics; 63 64 import androidx.test.filters.FlakyTest; 65 import androidx.test.rule.ActivityTestRule; 66 67 import com.android.compatibility.common.util.CtsTouchUtils; 68 import com.android.compatibility.common.util.SystemUtil; 69 70 import org.junit.Before; 71 import org.junit.Test; 72 73 import java.util.ArrayList; 74 import java.util.Random; 75 import java.util.Set; 76 import java.util.concurrent.ExecutorService; 77 import java.util.concurrent.Executors; 78 import java.util.concurrent.TimeUnit; 79 import java.util.concurrent.atomic.AtomicBoolean; 80 import java.util.concurrent.CompletableFuture; 81 import java.util.concurrent.TimeoutException; 82 83 /** 84 * Ensure moving windows and tapping is done synchronously. 85 * 86 * Build/Install/Run: 87 * atest CtsWindowManagerDeviceTestCases:WindowInputTests 88 */ 89 @Presubmit 90 public class WindowInputTests { 91 private static final String TAG = "WindowInputTests"; 92 private final int TOTAL_NUMBER_OF_CLICKS = 100; 93 private final ActivityTestRule<TestActivity> mActivityRule = 94 new ActivityTestRule<>(TestActivity.class); 95 private static final int TAPPING_TARGET_WINDOW_SIZE = 100; 96 private static final int PARTIAL_OBSCURING_WINDOW_SIZE = 30; 97 98 private Instrumentation mInstrumentation; 99 private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); 100 private TestActivity mActivity; 101 private InputManager mInputManager; 102 private View mView; 103 private final Random mRandom = new Random(1); 104 105 private int mClickCount = 0; 106 private final long EVENT_FLAGS_WAIT_TIME = 2; 107 108 @Before setUp()109 public void setUp() { 110 pressWakeupButton(); 111 pressUnlockButton(); 112 launchHomeActivityNoWait(); 113 114 mInstrumentation = getInstrumentation(); 115 mActivity = mActivityRule.launchActivity(null); 116 mInputManager = mActivity.getSystemService(InputManager.class); 117 mInstrumentation.waitForIdleSync(); 118 mClickCount = 0; 119 } 120 121 @Test 122 @FlakyTest(bugId = 188207199) testMoveWindowAndTap()123 public void testMoveWindowAndTap() throws Throwable { 124 final WindowManager wm = mActivity.getWindowManager(); 125 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 126 p.setFitInsetsTypes(WindowInsets.Type.systemBars() 127 | WindowInsets.Type.systemGestures()); 128 p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 129 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 130 p.width = p.height = 20; 131 p.gravity = Gravity.LEFT | Gravity.TOP; 132 133 // Set up window. 134 mActivityRule.runOnUiThread(() -> { 135 mView = new View(mActivity); 136 mView.setBackgroundColor(Color.RED); 137 mView.setOnClickListener((v) -> { 138 mClickCount++; 139 }); 140 mActivity.addWindow(mView, p); 141 }); 142 mInstrumentation.waitForIdleSync(); 143 144 final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); 145 final WindowInsets windowInsets = windowMetrics.getWindowInsets(); 146 final Rect windowBounds = new Rect(windowMetrics.getBounds()); 147 windowBounds.inset(windowInsets.getInsetsIgnoringVisibility(p.getFitInsetsTypes())); 148 149 // Move the window to a random location in the window and attempt to tap on view multiple 150 // times. 151 final Point locationInWindow = new Point(); 152 for (int i = 0; i < TOTAL_NUMBER_OF_CLICKS; i++) { 153 selectRandomLocationInWindow(windowBounds, locationInWindow); 154 mActivityRule.runOnUiThread(() -> { 155 p.x = locationInWindow.x; 156 p.y = locationInWindow.y; 157 wm.updateViewLayout(mView, p); 158 }); 159 mInstrumentation.waitForIdleSync(); 160 int previousCount = mClickCount; 161 162 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 163 164 mInstrumentation.waitForIdleSync(); 165 if (mClickCount != previousCount + 1) { 166 final int vW = mView.getWidth(); 167 final int vH = mView.getHeight(); 168 final int[] viewOnScreenXY = new int[2]; 169 mView.getLocationOnScreen(viewOnScreenXY); 170 final Point tapPosition = 171 new Point(viewOnScreenXY[0] + vW / 2, viewOnScreenXY[1] + vH / 2); 172 final Rect realBounds = new Rect(viewOnScreenXY[0], viewOnScreenXY[1], 173 viewOnScreenXY[0] + vW, viewOnScreenXY[1] + vH); 174 final Rect requestedBounds = new Rect(p.x, p.y, p.x + p.width, p.y + p.height); 175 dumpWindows("Dumping windows due to failure"); 176 fail("Tap #" + i + " on " + tapPosition + " failed; realBounds=" + realBounds 177 + " requestedBounds=" + requestedBounds); 178 } 179 } 180 181 assertEquals(TOTAL_NUMBER_OF_CLICKS, mClickCount); 182 } 183 dumpWindows(String message)184 private void dumpWindows(String message) { 185 Log.d(TAG, message); 186 mWmState.computeState(); 187 for (WindowState window : mWmState.getWindows()) { 188 Log.d(TAG, " => " + window.toLongString()); 189 } 190 } 191 selectRandomLocationInWindow(Rect bounds, Point outLocation)192 private void selectRandomLocationInWindow(Rect bounds, Point outLocation) { 193 int randomX = mRandom.nextInt(bounds.right - bounds.left) + bounds.left; 194 int randomY = mRandom.nextInt(bounds.bottom - bounds.top) + bounds.top; 195 outLocation.set(randomX, randomY); 196 } 197 198 @Test testTouchModalWindow()199 public void testTouchModalWindow() throws Throwable { 200 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 201 202 // Set up 2 touch modal windows, expect the last one will receive all touch events. 203 mActivityRule.runOnUiThread(() -> { 204 mView = new View(mActivity); 205 p.width = 20; 206 p.height = 20; 207 p.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; 208 mView.setFilterTouchesWhenObscured(true); 209 mView.setOnClickListener((v) -> { 210 mClickCount++; 211 }); 212 mActivity.addWindow(mView, p); 213 214 View view2 = new View(mActivity); 215 p.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 216 p.type = WindowManager.LayoutParams.TYPE_APPLICATION; 217 mActivity.addWindow(view2, p); 218 }); 219 mInstrumentation.waitForIdleSync(); 220 221 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 222 assertEquals(0, mClickCount); 223 } 224 225 // If a window is obscured by another window from the same app, touches should still get 226 // delivered to the bottom window, and the FLAG_WINDOW_IS_OBSCURED should not be set. 227 @Test testFilterTouchesWhenObscuredByWindowFromSameUid()228 public void testFilterTouchesWhenObscuredByWindowFromSameUid() throws Throwable { 229 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 230 231 final AtomicBoolean touchReceived = new AtomicBoolean(false); 232 final CompletableFuture<Integer> eventFlags = new CompletableFuture<>(); 233 // Set up a touchable window. 234 mActivityRule.runOnUiThread(() -> { 235 mView = new View(mActivity); 236 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 237 p.width = 100; 238 p.height = 100; 239 p.gravity = Gravity.CENTER; 240 mView.setFilterTouchesWhenObscured(true); 241 mView.setOnClickListener((v) -> { 242 mClickCount++; 243 }); 244 mView.setOnTouchListener((v, ev) -> { 245 touchReceived.set(true); 246 eventFlags.complete(ev.getFlags()); 247 return false; 248 }); 249 mActivity.addWindow(mView, p); 250 251 // Set up an overlap window, use same process. 252 View overlay = new View(mActivity); 253 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_NOT_TOUCHABLE; 254 p.width = 100; 255 p.height = 100; 256 p.gravity = Gravity.CENTER; 257 p.type = WindowManager.LayoutParams.TYPE_APPLICATION; 258 mActivity.addWindow(overlay, p); 259 }); 260 mInstrumentation.waitForIdleSync(); 261 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 262 263 assertTrue(touchReceived.get()); 264 assertEquals(0, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) 265 & MotionEvent.FLAG_WINDOW_IS_OBSCURED); 266 assertEquals(1, mClickCount); 267 } 268 269 @Test testFilterTouchesWhenObscuredByWindowFromDifferentUid()270 public void testFilterTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable { 271 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 272 273 final Intent intent = new Intent(); 274 intent.setComponent(Components.OVERLAY_TEST_SERVICE); 275 final String windowName = "Test Overlay"; 276 final AtomicBoolean touchReceived = new AtomicBoolean(false); 277 final int[] viewOnScreenLocation = new int[2]; 278 try { 279 // Set up a touchable window. 280 mActivityRule.runOnUiThread(() -> { 281 mView = new View(mActivity); 282 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 283 p.width = TAPPING_TARGET_WINDOW_SIZE; 284 p.height = TAPPING_TARGET_WINDOW_SIZE; 285 p.gravity = Gravity.CENTER; 286 mView.setFilterTouchesWhenObscured(true); 287 mView.setOnClickListener((v) -> { 288 mClickCount++; 289 }); 290 mView.setOnTouchListener((v, ev) -> { 291 touchReceived.set(true); 292 return false; 293 }); 294 mActivity.addWindow(mView, p); 295 }); 296 mInstrumentation.waitForIdleSync(); 297 mActivityRule.runOnUiThread(() -> { 298 mView.getLocationOnScreen(viewOnScreenLocation); 299 // Set up an overlap window from service, use different process. 300 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); 301 params.flags |= FLAG_NOT_TOUCHABLE; 302 placeWindowAtLayoutCenter(params, TAPPING_TARGET_WINDOW_SIZE, 303 viewOnScreenLocation[0], viewOnScreenLocation[1], 304 TAPPING_TARGET_WINDOW_SIZE); 305 // Any opacity higher than this would make InputDispatcher block the touch 306 params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(); 307 intent.putExtra(EXTRA_LAYOUT_PARAMS, params); 308 mActivity.startForegroundService(intent); 309 }); 310 mInstrumentation.waitForIdleSync(); 311 waitForWindow(windowName); 312 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 313 314 // Touch not received due to setFilterTouchesWhenObscured(true) 315 assertFalse(touchReceived.get()); 316 assertEquals(0, mClickCount); 317 } finally { 318 mActivity.stopService(intent); 319 } 320 } 321 322 @Test testFlagTouchesWhenObscuredByWindowFromDifferentUid()323 public void testFlagTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable { 324 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 325 326 final Intent intent = new Intent(); 327 intent.setComponent(Components.OVERLAY_TEST_SERVICE); 328 final String windowName = "Test Overlay"; 329 final AtomicBoolean touchReceived = new AtomicBoolean(false); 330 final CompletableFuture<Integer> eventFlags = new CompletableFuture<>(); 331 final int[] viewOnScreenLocation = new int[2]; 332 try { 333 // Set up a touchable window. 334 mActivityRule.runOnUiThread(() -> { 335 mView = new View(mActivity); 336 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 337 p.width = TAPPING_TARGET_WINDOW_SIZE; 338 p.height = TAPPING_TARGET_WINDOW_SIZE; 339 p.gravity = Gravity.CENTER; 340 mView.setOnClickListener((v) -> { 341 mClickCount++; 342 }); 343 mView.setOnTouchListener((v, ev) -> { 344 touchReceived.set(true); 345 eventFlags.complete(ev.getFlags()); 346 return false; 347 }); 348 mActivity.addWindow(mView, p); 349 }); 350 mInstrumentation.waitForIdleSync(); 351 mActivityRule.runOnUiThread(() -> { 352 mView.getLocationOnScreen(viewOnScreenLocation); 353 // Set up an overlap window from service, use different process. 354 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); 355 params.flags |= FLAG_NOT_TOUCHABLE; 356 // Any opacity higher than this would make InputDispatcher block the touch 357 params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(); 358 placeWindowAtLayoutCenter(params, TAPPING_TARGET_WINDOW_SIZE, 359 viewOnScreenLocation[0], viewOnScreenLocation[1], 360 TAPPING_TARGET_WINDOW_SIZE); 361 intent.putExtra(EXTRA_LAYOUT_PARAMS, params); 362 mActivity.startForegroundService(intent); 363 }); 364 waitForWindow(windowName); 365 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 366 367 assertTrue(touchReceived.get()); 368 assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED, 369 eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) 370 & MotionEvent.FLAG_WINDOW_IS_OBSCURED); 371 assertEquals(1, mClickCount); 372 } finally { 373 mActivity.stopService(intent); 374 } 375 } 376 377 @Test testDoNotFlagTouchesWhenObscuredByZeroOpacityWindow()378 public void testDoNotFlagTouchesWhenObscuredByZeroOpacityWindow() throws Throwable { 379 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 380 381 final Intent intent = new Intent(); 382 intent.setComponent(Components.OVERLAY_TEST_SERVICE); 383 final String windowName = "Test Overlay"; 384 final AtomicBoolean touchReceived = new AtomicBoolean(false); 385 final CompletableFuture<Integer> eventFlags = new CompletableFuture<>(); 386 387 try { 388 mActivityRule.runOnUiThread(() -> { 389 mView = new View(mActivity); 390 mView.setBackgroundColor(Color.GREEN); 391 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 392 p.width = 100; 393 p.height = 100; 394 p.gravity = Gravity.CENTER; 395 mView.setOnClickListener((v) -> { 396 mClickCount++; 397 }); 398 mView.setOnTouchListener((v, ev) -> { 399 touchReceived.set(true); 400 eventFlags.complete(ev.getFlags()); 401 return false; 402 }); 403 mActivity.addWindow(mView, p); 404 405 // Set up an overlap window from service, use different process. 406 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); 407 params.flags |= FLAG_NOT_TOUCHABLE; 408 params.alpha = 0; 409 intent.putExtra(EXTRA_LAYOUT_PARAMS, params); 410 mActivity.startForegroundService(intent); 411 }); 412 mInstrumentation.waitForIdleSync(); 413 waitForWindow(windowName); 414 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 415 416 assertTrue(touchReceived.get()); 417 assertEquals(0, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) 418 & MotionEvent.FLAG_WINDOW_IS_OBSCURED); 419 assertEquals(1, mClickCount); 420 } finally { 421 mActivity.stopService(intent); 422 } 423 } 424 425 @Test testFlagTouchesWhenObscuredByMinPositiveOpacityWindow()426 public void testFlagTouchesWhenObscuredByMinPositiveOpacityWindow() throws Throwable { 427 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 428 final CompletableFuture<Integer> eventFlags = new CompletableFuture<>(); 429 final Intent intent = new Intent(); 430 intent.setComponent(Components.OVERLAY_TEST_SERVICE); 431 final String windowName = "Test Overlay"; 432 final AtomicBoolean touchReceived = new AtomicBoolean(false); 433 final int[] viewOnScreenLocation = new int[2]; 434 try { 435 mActivityRule.runOnUiThread(() -> { 436 mView = new View(mActivity); 437 mView.setBackgroundColor(Color.GREEN); 438 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 439 p.width = TAPPING_TARGET_WINDOW_SIZE; 440 p.height = TAPPING_TARGET_WINDOW_SIZE; 441 p.gravity = Gravity.CENTER; 442 mView.setOnClickListener((v) -> { 443 mClickCount++; 444 }); 445 mView.setOnTouchListener((v, ev) -> { 446 touchReceived.set(true); 447 eventFlags.complete(ev.getFlags()); 448 return false; 449 }); 450 mActivity.addWindow(mView, p); 451 }); 452 mInstrumentation.waitForIdleSync(); 453 mActivityRule.runOnUiThread(() -> { 454 mView.getLocationOnScreen(viewOnScreenLocation); 455 // Set up an overlap window from service, use different process. 456 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); 457 params.flags |= FLAG_NOT_TOUCHABLE; 458 params.alpha = MIN_POSITIVE_OPACITY; 459 placeWindowAtLayoutCenter(params, TAPPING_TARGET_WINDOW_SIZE, 460 viewOnScreenLocation[0], viewOnScreenLocation[1], 461 TAPPING_TARGET_WINDOW_SIZE); 462 intent.putExtra(EXTRA_LAYOUT_PARAMS, params); 463 mActivity.startForegroundService(intent); 464 }); 465 mInstrumentation.waitForIdleSync(); 466 waitForWindow(windowName); 467 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 468 469 assertTrue(touchReceived.get()); 470 assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED, 471 eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) 472 & MotionEvent.FLAG_WINDOW_IS_OBSCURED); 473 assertEquals(1, mClickCount); 474 } finally { 475 mActivity.stopService(intent); 476 } 477 } 478 479 @Test testFlagTouchesWhenPartiallyObscuredByZeroOpacityWindow()480 public void testFlagTouchesWhenPartiallyObscuredByZeroOpacityWindow() throws Throwable { 481 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 482 483 final Intent intent = new Intent(); 484 intent.setComponent(Components.OVERLAY_TEST_SERVICE); 485 final String windowName = "Test Overlay"; 486 final AtomicBoolean touchReceived = new AtomicBoolean(false); 487 final CompletableFuture<Integer> eventFlags = new CompletableFuture<>(); 488 final int[] viewOnScreenLocation = new int[2]; 489 try { 490 mActivityRule.runOnUiThread(() -> { 491 mView = new View(mActivity); 492 mView.setBackgroundColor(Color.GREEN); 493 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 494 p.width = TAPPING_TARGET_WINDOW_SIZE; 495 p.height = TAPPING_TARGET_WINDOW_SIZE; 496 p.gravity = Gravity.CENTER; 497 mView.setOnClickListener((v) -> { 498 mClickCount++; 499 }); 500 mView.setOnTouchListener((v, ev) -> { 501 touchReceived.set(true); 502 eventFlags.complete(ev.getFlags()); 503 return false; 504 }); 505 mActivity.addWindow(mView, p); 506 }); 507 mInstrumentation.waitForIdleSync(); 508 mActivityRule.runOnUiThread(() -> { 509 mView.getLocationOnScreen(viewOnScreenLocation); 510 // Set up an overlap window from service, use different process. 511 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName, 512 PARTIAL_OBSCURING_WINDOW_SIZE); 513 placeWindowAtLayoutCenter(params, PARTIAL_OBSCURING_WINDOW_SIZE, 514 viewOnScreenLocation[0], viewOnScreenLocation[1], TAPPING_TARGET_WINDOW_SIZE); 515 // Move it off the touch path (center) but still overlap with window above 516 params.y += PARTIAL_OBSCURING_WINDOW_SIZE; 517 intent.putExtra(EXTRA_LAYOUT_PARAMS, params); 518 mActivity.startForegroundService(intent); 519 }); 520 mInstrumentation.waitForIdleSync(); 521 waitForWindow(windowName); 522 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 523 524 assertTrue(touchReceived.get()); 525 assertEquals(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED, 526 eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) 527 & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED); 528 assertEquals(1, mClickCount); 529 } finally { 530 mActivity.stopService(intent); 531 } 532 } 533 534 @Test testDoNotFlagTouchesWhenPartiallyObscuredByNotTouchableZeroOpacityWindow()535 public void testDoNotFlagTouchesWhenPartiallyObscuredByNotTouchableZeroOpacityWindow() 536 throws Throwable { 537 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 538 539 final Intent intent = new Intent(); 540 intent.setComponent(Components.OVERLAY_TEST_SERVICE); 541 final String windowName = "Test Overlay"; 542 final AtomicBoolean touchReceived = new AtomicBoolean(false); 543 final CompletableFuture<Integer> eventFlags = new CompletableFuture<>(); 544 545 try { 546 mActivityRule.runOnUiThread(() -> { 547 mView = new View(mActivity); 548 mView.setBackgroundColor(Color.GREEN); 549 p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 550 p.width = 100; 551 p.height = 100; 552 p.gravity = Gravity.CENTER; 553 mView.setOnClickListener((v) -> { 554 mClickCount++; 555 }); 556 mView.setOnTouchListener((v, ev) -> { 557 touchReceived.set(true); 558 eventFlags.complete(ev.getFlags()); 559 return false; 560 }); 561 mActivity.addWindow(mView, p); 562 563 // Set up an overlap window from service, use different process. 564 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName, 30); 565 params.flags |= FLAG_NOT_TOUCHABLE; 566 // Move it off the touch path (center) but still overlap with window above 567 params.y = 30; 568 params.alpha = 0; 569 intent.putExtra(EXTRA_LAYOUT_PARAMS, params); 570 mActivity.startForegroundService(intent); 571 }); 572 mInstrumentation.waitForIdleSync(); 573 waitForWindow(windowName); 574 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 575 576 assertTrue(touchReceived.get()); 577 assertEquals(0, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) 578 & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED); 579 assertEquals(1, mClickCount); 580 } finally { 581 mActivity.stopService(intent); 582 } 583 } 584 getObscuringViewLayoutParams(String windowName)585 private WindowManager.LayoutParams getObscuringViewLayoutParams(String windowName) { 586 return getObscuringViewLayoutParams(windowName, 100); 587 } 588 getObscuringViewLayoutParams(String windowName, int size)589 private WindowManager.LayoutParams getObscuringViewLayoutParams(String windowName, int size) { 590 WindowManager.LayoutParams params = new WindowManager.LayoutParams(); 591 params.setTitle(windowName); 592 params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 593 params.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; 594 params.width = size; 595 params.height = size; 596 params.gravity = Gravity.CENTER; 597 return params; 598 } 599 600 @Test testTrustedOverlapWindow()601 public void testTrustedOverlapWindow() throws Throwable { 602 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 603 try (final PointerLocationSession session = new PointerLocationSession()) { 604 session.set(true); 605 session.waitForReady(mActivity.getDisplayId()); 606 607 // Set up window. 608 mActivityRule.runOnUiThread(() -> { 609 mView = new View(mActivity); 610 p.width = 20; 611 p.height = 20; 612 p.gravity = Gravity.CENTER; 613 mView.setFilterTouchesWhenObscured(true); 614 mView.setOnClickListener((v) -> { 615 mClickCount++; 616 }); 617 mActivity.addWindow(mView, p); 618 619 }); 620 mInstrumentation.waitForIdleSync(); 621 622 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 623 } 624 assertEquals(1, mClickCount); 625 } 626 627 @Test testWindowBecomesUnTouchable()628 public void testWindowBecomesUnTouchable() throws Throwable { 629 final WindowManager wm = mActivity.getWindowManager(); 630 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 631 632 final View viewOverlap = new View(mActivity); 633 634 // Set up window. 635 mActivityRule.runOnUiThread(() -> { 636 mView = new View(mActivity); 637 p.width = 20; 638 p.height = 20; 639 p.gravity = Gravity.CENTER; 640 mView.setOnClickListener((v) -> { 641 mClickCount++; 642 }); 643 mActivity.addWindow(mView, p); 644 645 p.width = 100; 646 p.height = 100; 647 p.gravity = Gravity.CENTER; 648 p.type = WindowManager.LayoutParams.TYPE_APPLICATION; 649 mActivity.addWindow(viewOverlap, p); 650 }); 651 mInstrumentation.waitForIdleSync(); 652 653 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 654 assertEquals(0, mClickCount); 655 656 mActivityRule.runOnUiThread(() -> { 657 p.flags = FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE; 658 wm.updateViewLayout(viewOverlap, p); 659 }); 660 mInstrumentation.waitForIdleSync(); 661 662 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 663 assertEquals(1, mClickCount); 664 } 665 666 @Test testTapInsideUntouchableWindowResultInOutsideTouches()667 public void testTapInsideUntouchableWindowResultInOutsideTouches() throws Throwable { 668 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 669 670 final Set<MotionEvent> events = new ArraySet<>(); 671 mActivityRule.runOnUiThread(() -> { 672 mView = new View(mActivity); 673 p.width = 20; 674 p.height = 20; 675 p.gravity = Gravity.CENTER; 676 p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH; 677 mView.setOnTouchListener((v, e) -> { 678 // Copying to make sure we are not dealing with a reused object 679 events.add(MotionEvent.obtain(e)); 680 return false; 681 }); 682 mActivity.addWindow(mView, p); 683 }); 684 mInstrumentation.waitForIdleSync(); 685 686 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); 687 688 assertEquals(1, events.size()); 689 MotionEvent event = events.iterator().next(); 690 assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction()); 691 } 692 693 @Test testTapOutsideUntouchableWindowResultInOutsideTouches()694 public void testTapOutsideUntouchableWindowResultInOutsideTouches() throws Throwable { 695 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 696 697 Set<MotionEvent> events = new ArraySet<>(); 698 int size = 20; 699 mActivityRule.runOnUiThread(() -> { 700 mView = new View(mActivity); 701 p.width = size; 702 p.height = size; 703 p.gravity = Gravity.CENTER; 704 p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH; 705 mView.setOnTouchListener((v, e) -> { 706 // Copying to make sure we are not dealing with a reused object 707 events.add(MotionEvent.obtain(e)); 708 return false; 709 }); 710 mActivity.addWindow(mView, p); 711 }); 712 mInstrumentation.waitForIdleSync(); 713 714 CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, mView, size + 5, size + 5); 715 716 assertEquals(1, events.size()); 717 MotionEvent event = events.iterator().next(); 718 assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction()); 719 } 720 721 @Test testInjectToStatusBar()722 public void testInjectToStatusBar() { 723 // Try to inject event to status bar. 724 assumeHasStatusBar(mActivityRule); 725 final long downTime = SystemClock.uptimeMillis(); 726 final MotionEvent eventHover = MotionEvent.obtain( 727 downTime, downTime, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); 728 eventHover.setSource(InputDevice.SOURCE_MOUSE); 729 try { 730 mInstrumentation.sendPointerSync(eventHover); 731 fail("Not allowed to inject event to the window from another process."); 732 } catch (SecurityException e) { 733 // Should not be allowed to inject event to the window from another process. 734 } 735 } 736 737 @Test testInjectFromThread()738 public void testInjectFromThread() throws InterruptedException { 739 // Continually inject event to activity from thread. 740 final int[] decorViewLocation = new int[2]; 741 final View decorView = mActivity.getWindow().getDecorView(); 742 decorView.getLocationOnScreen(decorViewLocation); 743 // Tap at the center of the view. Calculate and tap at the absolute view center location on 744 // screen, so that the tapping location is always as expected regardless of windowing mode. 745 final Point testPoint = new Point(decorViewLocation[0] + decorView.getWidth() / 2, 746 decorViewLocation[1] + decorView.getHeight() / 2); 747 748 final long downTime = SystemClock.uptimeMillis(); 749 final MotionEvent eventDown = MotionEvent.obtain( 750 downTime, downTime, MotionEvent.ACTION_DOWN, testPoint.x, testPoint.y, 1); 751 mInstrumentation.sendPointerSync(eventDown); 752 753 final ExecutorService executor = Executors.newSingleThreadExecutor(); 754 boolean[] securityExceptionCaught = new boolean[1]; 755 executor.execute(() -> { 756 mInstrumentation.sendPointerSync(eventDown); 757 for (int i = 0; i < 20; i++) { 758 final long eventTime = SystemClock.uptimeMillis(); 759 final MotionEvent eventMove = MotionEvent.obtain( 760 downTime, eventTime, MotionEvent.ACTION_MOVE, testPoint.x, testPoint.y, 1); 761 try { 762 mInstrumentation.sendPointerSync(eventMove); 763 } catch (SecurityException e) { 764 securityExceptionCaught[0] = true; 765 } 766 } 767 }); 768 769 // Launch another activity, should not crash the process. 770 final Intent intent = new Intent(mActivity, TestActivity.class); 771 mActivityRule.launchActivity(intent); 772 mInstrumentation.waitForIdleSync(); 773 774 executor.shutdown(); 775 executor.awaitTermination(5L, TimeUnit.SECONDS); 776 777 if (securityExceptionCaught[0]) { 778 // Fail the test here instead of in the executor lambda, 779 // so the failure is thrown in the test thread. 780 fail("Should be allowed to inject event."); 781 } 782 } 783 waitForWindow(String name)784 private void waitForWindow(String name) { 785 mWmState.waitForWithAmState(state -> state.isWindowSurfaceShown(name), 786 name + "'s surface is appeared"); 787 } 788 789 public static class TestActivity extends Activity { 790 private ArrayList<View> mViews = new ArrayList<>(); 791 792 @Override onCreate(Bundle savedInstanceState)793 protected void onCreate(Bundle savedInstanceState) { 794 super.onCreate(savedInstanceState); 795 } 796 addWindow(View view, WindowManager.LayoutParams attrs)797 void addWindow(View view, WindowManager.LayoutParams attrs) { 798 getWindowManager().addView(view, attrs); 799 mViews.add(view); 800 } 801 removeAllWindows()802 void removeAllWindows() { 803 for (View view : mViews) { 804 getWindowManager().removeViewImmediate(view); 805 } 806 mViews.clear(); 807 } 808 809 @Override onPause()810 protected void onPause() { 811 super.onPause(); 812 removeAllWindows(); 813 } 814 } 815 816 /** Set a square window to display at the center of a square layout*/ placeWindowAtLayoutCenter(WindowManager.LayoutParams windowParams, int windowWidth, int layoutLeft, int layoutTop, int layoutWidth)817 static void placeWindowAtLayoutCenter(WindowManager.LayoutParams windowParams, int windowWidth, 818 int layoutLeft, int layoutTop, int layoutWidth) { 819 windowParams.gravity = Gravity.TOP | Gravity.LEFT; 820 int offset = (layoutWidth - windowWidth) / 2; 821 windowParams.x = layoutLeft + offset; 822 windowParams.y = layoutTop + offset; 823 } 824 825 /** Helper class to save, set, and restore pointer location preferences. */ 826 private static class PointerLocationSession extends SettingsSession<Boolean> { PointerLocationSession()827 PointerLocationSession() { 828 super(Settings.System.getUriFor("pointer_location" /* POINTER_LOCATION */), 829 PointerLocationSession::get, 830 PointerLocationSession::put); 831 } 832 put(ContentResolver contentResolver, String s, boolean v)833 private static void put(ContentResolver contentResolver, String s, boolean v) { 834 SystemUtil.runShellCommand( 835 "settings put system " + "pointer_location" + " " + (v ? 1 : 0)); 836 } 837 get(ContentResolver contentResolver, String s)838 private static boolean get(ContentResolver contentResolver, String s) { 839 try { 840 return Integer.parseInt(SystemUtil.runShellCommand( 841 "settings get system " + "pointer_location").trim()) == 1; 842 } catch (NumberFormatException e) { 843 return false; 844 } 845 } 846 847 // Wait until pointer location surface shown. waitForReady(int displayId)848 static void waitForReady(int displayId) { 849 final WindowManagerStateHelper wmState = new WindowManagerStateHelper(); 850 final String windowName = "PointerLocation - display " + displayId; 851 wmState.waitForWithAmState(state -> { 852 return state.isWindowSurfaceShown(windowName); 853 }, windowName + "'s surface is appeared"); 854 } 855 } 856 } 857