blob: 2e29a74d0abd7f8766c607f362442f6346248dde [file] [log] [blame]
/*
* 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.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 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<TestActivity> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<MotionEvent> 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<MotionEvent> 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 to windows owned by another uid from Instrumentation.");
} catch (RuntimeException e) {
// Should not be allowed to inject event to a window owned by another uid from the
// Instrumentation class.
}
}
@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<View> 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<Boolean> {
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");
}
}
}