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