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