/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package android.server.wm; import static android.server.wm.ActivityManagerTestBase.launchHomeActivityNoWait; import static android.server.wm.BarTestUtils.assumeHasStatusBar; import static android.server.wm.UiDeviceUtils.pressUnlockButton; import static android.server.wm.UiDeviceUtils.pressWakeupButton; import static android.server.wm.WindowUntrustedTouchTest.MIN_POSITIVE_OPACITY; import static android.server.wm.app.Components.OverlayTestService.EXTRA_LAYOUT_PARAMS; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import android.app.Activity; import android.app.Instrumentation; import android.content.ContentResolver; import android.content.Intent; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Bundle; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.server.wm.WindowManagerState.WindowState; import android.server.wm.app.Components; import android.server.wm.settings.SettingsSession; import android.util.ArraySet; import android.util.Log; import android.view.Gravity; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; import androidx.test.filters.FlakyTest; import androidx.test.rule.ActivityTestRule; import com.android.compatibility.common.util.CtsTouchUtils; import com.android.compatibility.common.util.SystemUtil; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; /** * Ensure moving windows and tapping is done synchronously. * * Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:WindowInputTests */ @Presubmit public class WindowInputTests { private static final String TAG = "WindowInputTests"; private final int TOTAL_NUMBER_OF_CLICKS = 100; private final ActivityTestRule mActivityRule = new ActivityTestRule<>(TestActivity.class); private static final int TAPPING_TARGET_WINDOW_SIZE = 100; private static final int PARTIAL_OBSCURING_WINDOW_SIZE = 30; private Instrumentation mInstrumentation; private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); private TestActivity mActivity; private InputManager mInputManager; private View mView; private final Random mRandom = new Random(1); private int mClickCount = 0; private final long EVENT_FLAGS_WAIT_TIME = 2; @Before public void setUp() { pressWakeupButton(); pressUnlockButton(); launchHomeActivityNoWait(); mInstrumentation = getInstrumentation(); mActivity = mActivityRule.launchActivity(null); mInputManager = mActivity.getSystemService(InputManager.class); mInstrumentation.waitForIdleSync(); mClickCount = 0; } @Test @FlakyTest(bugId = 188207199) public void testMoveWindowAndTap() throws Throwable { final WindowManager wm = mActivity.getWindowManager(); final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); p.setFitInsetsTypes(WindowInsets.Type.systemBars() | WindowInsets.Type.systemGestures()); p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; p.width = p.height = 20; p.gravity = Gravity.LEFT | Gravity.TOP; // Set up window. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); mView.setBackgroundColor(Color.RED); mView.setOnClickListener((v) -> { mClickCount++; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); final WindowInsets windowInsets = windowMetrics.getWindowInsets(); final Rect windowBounds = new Rect(windowMetrics.getBounds()); windowBounds.inset(windowInsets.getInsetsIgnoringVisibility(p.getFitInsetsTypes())); // Move the window to a random location in the window and attempt to tap on view multiple // times. final Point locationInWindow = new Point(); for (int i = 0; i < TOTAL_NUMBER_OF_CLICKS; i++) { selectRandomLocationInWindow(windowBounds, locationInWindow); mActivityRule.runOnUiThread(() -> { p.x = locationInWindow.x; p.y = locationInWindow.y; wm.updateViewLayout(mView, p); }); mInstrumentation.waitForIdleSync(); int previousCount = mClickCount; CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); mInstrumentation.waitForIdleSync(); if (mClickCount != previousCount + 1) { final int vW = mView.getWidth(); final int vH = mView.getHeight(); final int[] viewOnScreenXY = new int[2]; mView.getLocationOnScreen(viewOnScreenXY); final Point tapPosition = new Point(viewOnScreenXY[0] + vW / 2, viewOnScreenXY[1] + vH / 2); final Rect realBounds = new Rect(viewOnScreenXY[0], viewOnScreenXY[1], viewOnScreenXY[0] + vW, viewOnScreenXY[1] + vH); final Rect requestedBounds = new Rect(p.x, p.y, p.x + p.width, p.y + p.height); dumpWindows("Dumping windows due to failure"); fail("Tap #" + i + " on " + tapPosition + " failed; realBounds=" + realBounds + " requestedBounds=" + requestedBounds); } } assertEquals(TOTAL_NUMBER_OF_CLICKS, mClickCount); } private void dumpWindows(String message) { Log.d(TAG, message); mWmState.computeState(); for (WindowState window : mWmState.getWindows()) { Log.d(TAG, " => " + window.toLongString()); } } private void selectRandomLocationInWindow(Rect bounds, Point outLocation) { int randomX = mRandom.nextInt(bounds.right - bounds.left) + bounds.left; int randomY = mRandom.nextInt(bounds.bottom - bounds.top) + bounds.top; outLocation.set(randomX, randomY); } @Test public void testTouchModalWindow() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); // Set up 2 touch modal windows, expect the last one will receive all touch events. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.width = 20; p.height = 20; p.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; mView.setFilterTouchesWhenObscured(true); mView.setOnClickListener((v) -> { mClickCount++; }); mActivity.addWindow(mView, p); View view2 = new View(mActivity); p.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; p.type = WindowManager.LayoutParams.TYPE_APPLICATION; mActivity.addWindow(view2, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertEquals(0, mClickCount); } // If a window is obscured by another window from the same app, touches should still get // delivered to the bottom window, and the FLAG_WINDOW_IS_OBSCURED should not be set. @Test public void testFilterTouchesWhenObscuredByWindowFromSameUid() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final AtomicBoolean touchReceived = new AtomicBoolean(false); final CompletableFuture eventFlags = new CompletableFuture<>(); // Set up a touchable window. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = 100; p.height = 100; p.gravity = Gravity.CENTER; mView.setFilterTouchesWhenObscured(true); mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); eventFlags.complete(ev.getFlags()); return false; }); mActivity.addWindow(mView, p); // Set up an overlap window, use same process. View overlay = new View(mActivity); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_NOT_TOUCHABLE; p.width = 100; p.height = 100; p.gravity = Gravity.CENTER; p.type = WindowManager.LayoutParams.TYPE_APPLICATION; mActivity.addWindow(overlay, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertTrue(touchReceived.get()); assertEquals(0, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) & MotionEvent.FLAG_WINDOW_IS_OBSCURED); assertEquals(1, mClickCount); } @Test public void testFilterTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final Intent intent = new Intent(); intent.setComponent(Components.OVERLAY_TEST_SERVICE); final String windowName = "Test Overlay"; final AtomicBoolean touchReceived = new AtomicBoolean(false); final int[] viewOnScreenLocation = new int[2]; try { // Set up a touchable window. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = TAPPING_TARGET_WINDOW_SIZE; p.height = TAPPING_TARGET_WINDOW_SIZE; p.gravity = Gravity.CENTER; mView.setFilterTouchesWhenObscured(true); mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); return false; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); mActivityRule.runOnUiThread(() -> { mView.getLocationOnScreen(viewOnScreenLocation); // Set up an overlap window from service, use different process. WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); params.flags |= FLAG_NOT_TOUCHABLE; placeWindowAtLayoutCenter(params, TAPPING_TARGET_WINDOW_SIZE, viewOnScreenLocation[0], viewOnScreenLocation[1], TAPPING_TARGET_WINDOW_SIZE); // Any opacity higher than this would make InputDispatcher block the touch params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(); intent.putExtra(EXTRA_LAYOUT_PARAMS, params); mActivity.startForegroundService(intent); }); mInstrumentation.waitForIdleSync(); waitForWindow(windowName); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); // Touch not received due to setFilterTouchesWhenObscured(true) assertFalse(touchReceived.get()); assertEquals(0, mClickCount); } finally { mActivity.stopService(intent); } } @Test public void testFlagTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final Intent intent = new Intent(); intent.setComponent(Components.OVERLAY_TEST_SERVICE); final String windowName = "Test Overlay"; final AtomicBoolean touchReceived = new AtomicBoolean(false); final CompletableFuture eventFlags = new CompletableFuture<>(); final int[] viewOnScreenLocation = new int[2]; try { // Set up a touchable window. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = TAPPING_TARGET_WINDOW_SIZE; p.height = TAPPING_TARGET_WINDOW_SIZE; p.gravity = Gravity.CENTER; mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); eventFlags.complete(ev.getFlags()); return false; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); mActivityRule.runOnUiThread(() -> { mView.getLocationOnScreen(viewOnScreenLocation); // Set up an overlap window from service, use different process. WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); params.flags |= FLAG_NOT_TOUCHABLE; // Any opacity higher than this would make InputDispatcher block the touch params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(); placeWindowAtLayoutCenter(params, TAPPING_TARGET_WINDOW_SIZE, viewOnScreenLocation[0], viewOnScreenLocation[1], TAPPING_TARGET_WINDOW_SIZE); intent.putExtra(EXTRA_LAYOUT_PARAMS, params); mActivity.startForegroundService(intent); }); waitForWindow(windowName); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertTrue(touchReceived.get()); assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) & MotionEvent.FLAG_WINDOW_IS_OBSCURED); assertEquals(1, mClickCount); } finally { mActivity.stopService(intent); } } @Test public void testDoNotFlagTouchesWhenObscuredByZeroOpacityWindow() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final Intent intent = new Intent(); intent.setComponent(Components.OVERLAY_TEST_SERVICE); final String windowName = "Test Overlay"; final AtomicBoolean touchReceived = new AtomicBoolean(false); final CompletableFuture eventFlags = new CompletableFuture<>(); try { mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); mView.setBackgroundColor(Color.GREEN); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = 100; p.height = 100; p.gravity = Gravity.CENTER; mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); eventFlags.complete(ev.getFlags()); return false; }); mActivity.addWindow(mView, p); // Set up an overlap window from service, use different process. WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); params.flags |= FLAG_NOT_TOUCHABLE; params.alpha = 0; intent.putExtra(EXTRA_LAYOUT_PARAMS, params); mActivity.startForegroundService(intent); }); mInstrumentation.waitForIdleSync(); waitForWindow(windowName); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertTrue(touchReceived.get()); assertEquals(0, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) & MotionEvent.FLAG_WINDOW_IS_OBSCURED); assertEquals(1, mClickCount); } finally { mActivity.stopService(intent); } } @Test public void testFlagTouchesWhenObscuredByMinPositiveOpacityWindow() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final CompletableFuture eventFlags = new CompletableFuture<>(); final Intent intent = new Intent(); intent.setComponent(Components.OVERLAY_TEST_SERVICE); final String windowName = "Test Overlay"; final AtomicBoolean touchReceived = new AtomicBoolean(false); final int[] viewOnScreenLocation = new int[2]; try { mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); mView.setBackgroundColor(Color.GREEN); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = TAPPING_TARGET_WINDOW_SIZE; p.height = TAPPING_TARGET_WINDOW_SIZE; p.gravity = Gravity.CENTER; mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); eventFlags.complete(ev.getFlags()); return false; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); mActivityRule.runOnUiThread(() -> { mView.getLocationOnScreen(viewOnScreenLocation); // Set up an overlap window from service, use different process. WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName); params.flags |= FLAG_NOT_TOUCHABLE; params.alpha = MIN_POSITIVE_OPACITY; placeWindowAtLayoutCenter(params, TAPPING_TARGET_WINDOW_SIZE, viewOnScreenLocation[0], viewOnScreenLocation[1], TAPPING_TARGET_WINDOW_SIZE); intent.putExtra(EXTRA_LAYOUT_PARAMS, params); mActivity.startForegroundService(intent); }); mInstrumentation.waitForIdleSync(); waitForWindow(windowName); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertTrue(touchReceived.get()); assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) & MotionEvent.FLAG_WINDOW_IS_OBSCURED); assertEquals(1, mClickCount); } finally { mActivity.stopService(intent); } } @Test public void testFlagTouchesWhenPartiallyObscuredByZeroOpacityWindow() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final Intent intent = new Intent(); intent.setComponent(Components.OVERLAY_TEST_SERVICE); final String windowName = "Test Overlay"; final AtomicBoolean touchReceived = new AtomicBoolean(false); final CompletableFuture eventFlags = new CompletableFuture<>(); final int[] viewOnScreenLocation = new int[2]; try { mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); mView.setBackgroundColor(Color.GREEN); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = TAPPING_TARGET_WINDOW_SIZE; p.height = TAPPING_TARGET_WINDOW_SIZE; p.gravity = Gravity.CENTER; mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); eventFlags.complete(ev.getFlags()); return false; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); mActivityRule.runOnUiThread(() -> { mView.getLocationOnScreen(viewOnScreenLocation); // Set up an overlap window from service, use different process. WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName, PARTIAL_OBSCURING_WINDOW_SIZE); placeWindowAtLayoutCenter(params, PARTIAL_OBSCURING_WINDOW_SIZE, viewOnScreenLocation[0], viewOnScreenLocation[1], TAPPING_TARGET_WINDOW_SIZE); // Move it off the touch path (center) but still overlap with window above params.y += PARTIAL_OBSCURING_WINDOW_SIZE; intent.putExtra(EXTRA_LAYOUT_PARAMS, params); mActivity.startForegroundService(intent); }); mInstrumentation.waitForIdleSync(); waitForWindow(windowName); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertTrue(touchReceived.get()); assertEquals(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED); assertEquals(1, mClickCount); } finally { mActivity.stopService(intent); } } @Test public void testDoNotFlagTouchesWhenPartiallyObscuredByNotTouchableZeroOpacityWindow() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final Intent intent = new Intent(); intent.setComponent(Components.OVERLAY_TEST_SERVICE); final String windowName = "Test Overlay"; final AtomicBoolean touchReceived = new AtomicBoolean(false); final CompletableFuture eventFlags = new CompletableFuture<>(); try { mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); mView.setBackgroundColor(Color.GREEN); p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; p.width = 100; p.height = 100; p.gravity = Gravity.CENTER; mView.setOnClickListener((v) -> { mClickCount++; }); mView.setOnTouchListener((v, ev) -> { touchReceived.set(true); eventFlags.complete(ev.getFlags()); return false; }); mActivity.addWindow(mView, p); // Set up an overlap window from service, use different process. WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName, 30); params.flags |= FLAG_NOT_TOUCHABLE; // Move it off the touch path (center) but still overlap with window above params.y = 30; params.alpha = 0; intent.putExtra(EXTRA_LAYOUT_PARAMS, params); mActivity.startForegroundService(intent); }); mInstrumentation.waitForIdleSync(); waitForWindow(windowName); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertTrue(touchReceived.get()); assertEquals(0, eventFlags.get(EVENT_FLAGS_WAIT_TIME, TimeUnit.SECONDS) & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED); assertEquals(1, mClickCount); } finally { mActivity.stopService(intent); } } private WindowManager.LayoutParams getObscuringViewLayoutParams(String windowName) { return getObscuringViewLayoutParams(windowName, 100); } private WindowManager.LayoutParams getObscuringViewLayoutParams(String windowName, int size) { WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.setTitle(windowName); params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; params.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN; params.width = size; params.height = size; params.gravity = Gravity.CENTER; return params; } @Test public void testTrustedOverlapWindow() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); try (final PointerLocationSession session = new PointerLocationSession()) { session.set(true); session.waitForReady(mActivity.getDisplayId()); // Set up window. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.width = 20; p.height = 20; p.gravity = Gravity.CENTER; mView.setFilterTouchesWhenObscured(true); mView.setOnClickListener((v) -> { mClickCount++; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); } assertEquals(1, mClickCount); } @Test public void testWindowBecomesUnTouchable() throws Throwable { final WindowManager wm = mActivity.getWindowManager(); final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final View viewOverlap = new View(mActivity); // Set up window. mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.width = 20; p.height = 20; p.gravity = Gravity.CENTER; mView.setOnClickListener((v) -> { mClickCount++; }); mActivity.addWindow(mView, p); p.width = 100; p.height = 100; p.gravity = Gravity.CENTER; p.type = WindowManager.LayoutParams.TYPE_APPLICATION; mActivity.addWindow(viewOverlap, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertEquals(0, mClickCount); mActivityRule.runOnUiThread(() -> { p.flags = FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE; wm.updateViewLayout(viewOverlap, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertEquals(1, mClickCount); } @Test public void testTapInsideUntouchableWindowResultInOutsideTouches() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); final Set events = new ArraySet<>(); mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.width = 20; p.height = 20; p.gravity = Gravity.CENTER; p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH; mView.setOnTouchListener((v, e) -> { // Copying to make sure we are not dealing with a reused object events.add(MotionEvent.obtain(e)); return false; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView); assertEquals(1, events.size()); MotionEvent event = events.iterator().next(); assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction()); } @Test public void testTapOutsideUntouchableWindowResultInOutsideTouches() throws Throwable { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); Set events = new ArraySet<>(); int size = 20; mActivityRule.runOnUiThread(() -> { mView = new View(mActivity); p.width = size; p.height = size; p.gravity = Gravity.CENTER; p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH; mView.setOnTouchListener((v, e) -> { // Copying to make sure we are not dealing with a reused object events.add(MotionEvent.obtain(e)); return false; }); mActivity.addWindow(mView, p); }); mInstrumentation.waitForIdleSync(); CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, mView, size + 5, size + 5); assertEquals(1, events.size()); MotionEvent event = events.iterator().next(); assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction()); } @Test public void testInjectToStatusBar() { // Try to inject event to status bar. assumeHasStatusBar(mActivityRule); final long downTime = SystemClock.uptimeMillis(); final MotionEvent eventHover = MotionEvent.obtain( downTime, downTime, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); eventHover.setSource(InputDevice.SOURCE_MOUSE); try { mInstrumentation.sendPointerSync(eventHover); fail("Not allowed to inject event to the window from another process."); } catch (SecurityException e) { // Should not be allowed to inject event to the window from another process. } } @Test public void testInjectFromThread() throws InterruptedException { // Continually inject event to activity from thread. final int[] decorViewLocation = new int[2]; final View decorView = mActivity.getWindow().getDecorView(); decorView.getLocationOnScreen(decorViewLocation); // Tap at the center of the view. Calculate and tap at the absolute view center location on // screen, so that the tapping location is always as expected regardless of windowing mode. final Point testPoint = new Point(decorViewLocation[0] + decorView.getWidth() / 2, decorViewLocation[1] + decorView.getHeight() / 2); final long downTime = SystemClock.uptimeMillis(); final MotionEvent eventDown = MotionEvent.obtain( downTime, downTime, MotionEvent.ACTION_DOWN, testPoint.x, testPoint.y, 1); mInstrumentation.sendPointerSync(eventDown); final ExecutorService executor = Executors.newSingleThreadExecutor(); boolean[] securityExceptionCaught = new boolean[1]; executor.execute(() -> { mInstrumentation.sendPointerSync(eventDown); for (int i = 0; i < 20; i++) { final long eventTime = SystemClock.uptimeMillis(); final MotionEvent eventMove = MotionEvent.obtain( downTime, eventTime, MotionEvent.ACTION_MOVE, testPoint.x, testPoint.y, 1); try { mInstrumentation.sendPointerSync(eventMove); } catch (SecurityException e) { securityExceptionCaught[0] = true; } } }); // Launch another activity, should not crash the process. final Intent intent = new Intent(mActivity, TestActivity.class); mActivityRule.launchActivity(intent); mInstrumentation.waitForIdleSync(); executor.shutdown(); executor.awaitTermination(5L, TimeUnit.SECONDS); if (securityExceptionCaught[0]) { // Fail the test here instead of in the executor lambda, // so the failure is thrown in the test thread. fail("Should be allowed to inject event."); } } private void waitForWindow(String name) { mWmState.waitForWithAmState(state -> state.isWindowSurfaceShown(name), name + "'s surface is appeared"); } public static class TestActivity extends Activity { private ArrayList mViews = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } void addWindow(View view, WindowManager.LayoutParams attrs) { getWindowManager().addView(view, attrs); mViews.add(view); } void removeAllWindows() { for (View view : mViews) { getWindowManager().removeViewImmediate(view); } mViews.clear(); } @Override protected void onPause() { super.onPause(); removeAllWindows(); } } /** Set a square window to display at the center of a square layout*/ static void placeWindowAtLayoutCenter(WindowManager.LayoutParams windowParams, int windowWidth, int layoutLeft, int layoutTop, int layoutWidth) { windowParams.gravity = Gravity.TOP | Gravity.LEFT; int offset = (layoutWidth - windowWidth) / 2; windowParams.x = layoutLeft + offset; windowParams.y = layoutTop + offset; } /** Helper class to save, set, and restore pointer location preferences. */ private static class PointerLocationSession extends SettingsSession { PointerLocationSession() { super(Settings.System.getUriFor("pointer_location" /* POINTER_LOCATION */), PointerLocationSession::get, PointerLocationSession::put); } private static void put(ContentResolver contentResolver, String s, boolean v) { SystemUtil.runShellCommand( "settings put system " + "pointer_location" + " " + (v ? 1 : 0)); } private static boolean get(ContentResolver contentResolver, String s) { try { return Integer.parseInt(SystemUtil.runShellCommand( "settings get system " + "pointer_location").trim()) == 1; } catch (NumberFormatException e) { return false; } } // Wait until pointer location surface shown. static void waitForReady(int displayId) { final WindowManagerStateHelper wmState = new WindowManagerStateHelper(); final String windowName = "PointerLocation - display " + displayId; wmState.waitForWithAmState(state -> { return state.isWindowSurfaceShown(windowName); }, windowName + "'s surface is appeared"); } } }