• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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