1 /*
2  * Copyright (C) 2024 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.input;
18 
19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
22 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED;
23 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
24 import static android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry;
25 import static android.view.Display.DEFAULT_DISPLAY;
26 import static android.view.Display.INVALID_DISPLAY;
27 import static android.view.KeyEvent.ACTION_DOWN;
28 import static android.view.KeyEvent.ACTION_UP;
29 import static android.view.KeyEvent.FLAG_CANCELED;
30 import static android.view.KeyEvent.KEYCODE_0;
31 import static android.view.KeyEvent.KEYCODE_1;
32 import static android.view.KeyEvent.KEYCODE_2;
33 import static android.view.KeyEvent.KEYCODE_3;
34 import static android.view.KeyEvent.KEYCODE_4;
35 import static android.view.KeyEvent.KEYCODE_5;
36 import static android.view.KeyEvent.KEYCODE_6;
37 import static android.view.KeyEvent.KEYCODE_7;
38 import static android.view.KeyEvent.KEYCODE_8;
39 import static android.view.KeyEvent.KEYCODE_9;
40 import static android.view.KeyEvent.keyCodeToString;
41 
42 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
43 
44 import static org.junit.Assert.assertEquals;
45 import static org.junit.Assert.assertFalse;
46 import static org.junit.Assert.assertNotNull;
47 import static org.junit.Assert.assertTrue;
48 import static org.junit.Assume.assumeFalse;
49 import static org.junit.Assume.assumeTrue;
50 
51 import android.app.Activity;
52 import android.content.Context;
53 import android.content.res.Configuration;
54 import android.graphics.Canvas;
55 import android.graphics.PixelFormat;
56 import android.graphics.Point;
57 import android.hardware.display.DisplayManager;
58 import android.hardware.display.VirtualDisplay;
59 import android.media.ImageReader;
60 import android.os.SystemClock;
61 import android.platform.test.annotations.Presubmit;
62 import android.server.wm.BuildUtils;
63 import android.server.wm.StateLogger;
64 import android.server.wm.WindowManagerState;
65 import android.server.wm.WindowManagerTestBase;
66 import android.view.Display;
67 import android.view.KeyEvent;
68 import android.view.MotionEvent;
69 import android.view.View;
70 import android.view.WindowManager.LayoutParams;
71 
72 import androidx.annotation.NonNull;
73 
74 import com.android.compatibility.common.util.SystemUtil;
75 import com.android.cts.input.DebugInputRule;
76 
77 import org.junit.Rule;
78 import org.junit.Test;
79 
80 import java.util.ArrayList;
81 import java.util.concurrent.TimeUnit;
82 
83 import javax.annotation.concurrent.GuardedBy;
84 
85 /**
86  * Ensure window focus assignment is executed as expected.
87  *
88  * Build/Install/Run:
89  *     atest CtsWindowManagerDeviceInput:WindowFocusTests
90  */
91 @Presubmit
92 public class WindowFocusTests extends WindowManagerTestBase {
93 
94     @Rule public DebugInputRule mDebugInputRule = new DebugInputRule();
95 
sendKey(int action, int keyCode, int displayId)96     private static void sendKey(int action, int keyCode, int displayId) {
97         final KeyEvent keyEvent = new KeyEvent(action, keyCode);
98         keyEvent.setDisplayId(displayId);
99         getInstrumentation().sendKeySync(keyEvent);
100     }
101 
sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode, int targetDisplayId)102     private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode,
103             int targetDisplayId) {
104         sendAndAssertTargetConsumedKey(target, ACTION_DOWN, keyCode, targetDisplayId);
105         sendAndAssertTargetConsumedKey(target, ACTION_UP, keyCode, targetDisplayId);
106     }
107 
sendAndAssertTargetConsumedKey(InputTargetActivity target, int action, int keyCode, int targetDisplayId)108     private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int action,
109             int keyCode, int targetDisplayId) {
110         final int eventCount = target.getKeyEventCount();
111         sendKey(action, keyCode, targetDisplayId);
112         target.assertAndConsumeKeyEvent(action, keyCode, 0 /* flags */);
113         assertEquals(target.getLogTag() + " must only receive key event sent.", eventCount,
114                 target.getKeyEventCount());
115     }
116 
tapOn(@onNull Activity activity)117     private static void tapOn(@NonNull Activity activity) {
118         final Point p = getCenterOfActivityOnScreen(activity);
119         final int displayId = activity.getDisplayId();
120 
121         final long downTime = SystemClock.elapsedRealtime();
122         final MotionEvent downEvent = MotionEvent.obtain(downTime, downTime,
123                 MotionEvent.ACTION_DOWN, p.x, p.y, 0 /* metaState */);
124         downEvent.setDisplayId(displayId);
125         getInstrumentation().sendPointerSync(downEvent);
126         final MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.elapsedRealtime(),
127                 MotionEvent.ACTION_UP, p.x, p.y, 0 /* metaState */);
128         upEvent.setDisplayId(displayId);
129         getInstrumentation().sendPointerSync(upEvent);
130     }
131 
getCenterOfActivityOnScreen(@onNull Activity activity)132     private static Point getCenterOfActivityOnScreen(@NonNull Activity activity) {
133         final View decorView = activity.getWindow().getDecorView();
134         final int[] location = new int[2];
135         decorView.getLocationOnScreen(location);
136         return new Point(location[0] + decorView.getWidth() / 2,
137                 location[1] + decorView.getHeight() / 2);
138     }
139 
140     /**
141      * Test the following conditions:
142      * - Each display can have a focused window at the same time.
143      * - Focused windows can receive display-specified key events.
144      * - The top focused window can receive display-unspecified key events.
145      * - Taping on a display will make the focused window on it become top-focused.
146      * - The window which lost top-focus can receive display-unspecified cancel events.
147      */
148     @Test
testKeyReceiving()149     public void testKeyReceiving() {
150         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
151                 DEFAULT_DISPLAY);
152         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY);
153         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
154 
155         assumeTrue(supportsMultiDisplay());
156 
157         // VirtualDisplay can't maintain perDisplayFocus because it is not trusted,
158         // so uses SimulatedDisplay instead.
159         final SimulatedDisplaySession session = createManagedSimulatedDisplaySession();
160         final int secondaryDisplayId = session.getDisplayId();
161         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
162         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
163         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId);
164 
165         // After launching the second activity the primary activities focus depends on the state of
166         // perDisplayFocusEnabled. If the display has its own focus, then the activities still has
167         // window focus. If it is disabled, then primary activity should no longer have window focus
168         // because the secondary activity got it.
169         primaryActivity.waitAndAssertWindowFocusState(perDisplayFocusEnabled());
170 
171         // Press display-unspecified keys and a display-specified key but not release them.
172         sendKey(ACTION_DOWN, KEYCODE_5, INVALID_DISPLAY);
173         sendKey(ACTION_DOWN, KEYCODE_6, secondaryDisplayId);
174         sendKey(ACTION_DOWN, KEYCODE_7, INVALID_DISPLAY);
175         secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_5, 0 /* flags */);
176         secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_6, 0 /* flags */);
177         secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_7, 0 /* flags */);
178 
179         tapOn(primaryActivity);
180 
181         // Assert only display-unspecified key would be cancelled after secondary activity is
182         // not top focused if per-display focus is enabled. Otherwise, assert all non-released
183         // key events sent to secondary activity would be cancelled.
184         secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_5, FLAG_CANCELED);
185         secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_7, FLAG_CANCELED);
186         if (!perDisplayFocusEnabled()) {
187             secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
188         }
189         assertEquals(secondaryActivity.getLogTag() + " must only receive expected events",
190                 0 /* expected event count */, secondaryActivity.getKeyEventCount());
191 
192         // Assert primary activity become top focused after tapping on default display.
193         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_8, INVALID_DISPLAY);
194     }
195 
196     @Test
testKeyReceivingWithDisplayWithOwnFocus()197     public void testKeyReceivingWithDisplayWithOwnFocus() {
198         assumeTrue(supportsMultiDisplay());
199         // This test specifically tests the behavior if a single display manages its own focus.
200         // Key receiving with perDisplayFocusEnabled is handled in #testKeyReceiving()
201         assumeFalse(perDisplayFocusEnabled());
202 
203         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
204                 DEFAULT_DISPLAY);
205 
206         final VirtualDisplayWithOwnFocusSession session =
207                 createManagedVirtualDisplayWithOwnFocusSession();
208         final int secondaryDisplayId = session.getDisplayId();
209         final SecondaryActivity secondaryActivity = session.startActivityAndFocus(
210                 SecondaryActivity.class);
211 
212         // The secondary display and activity gained focus; the window on default display
213         // has no longer focus because the secondary display is also the top display.
214         primaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ false);
215         secondaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ true);
216 
217         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_0, INVALID_DISPLAY);
218         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_1, secondaryDisplayId);
219 
220         // Send a key event to the primary activity on the default display to make it the top
221         // focused display.; the secondary ones did not lose window focus.
222         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_6, DEFAULT_DISPLAY);
223         primaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ true);
224         secondaryActivity.waitAndAssertWindowFocusState(/* hasFocus= */ true);
225 
226         // Assert primary activity become top focused after sending targeted key to default display
227         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_8, INVALID_DISPLAY);
228         // And targeted keys to the secondary display should still arrive at the secondary
229         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_9, secondaryDisplayId);
230 
231         assertEquals(secondaryActivity.getLogTag() + " must only receive expected events",
232                 0 /* expected event count */, secondaryActivity.getKeyEventCount());
233     }
234 
235     /**
236      * Test the {@link Display#FLAG_OWN_FOCUS} behavior.
237      * The flag is similar to {@link #perDisplayFocusEnabled()} but instead of affecting all
238      * displays it only affects the displays that have the flag set.
239      */
240     @Test
testOwnFocus()241     public void testOwnFocus() {
242         assumeTrue(supportsMultiDisplay());
243 
244         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
245                 DEFAULT_DISPLAY);
246 
247         // Create two VirtualDisplays with its own focus and launch an activity on them
248         final VirtualDisplayWithOwnFocusSession secondarySession =
249                 createManagedVirtualDisplayWithOwnFocusSession();
250         final SecondaryActivity secondaryActivity = secondarySession.startActivityAndFocus(
251                 SecondaryActivity.class);
252         final VirtualDisplayWithOwnFocusSession tertiarySession =
253                 createManagedVirtualDisplayWithOwnFocusSession();
254         final TertiaryActivity tertiaryActivity = tertiarySession.startActivityAndFocus(
255                 TertiaryActivity.class);
256 
257         // The primary activity will have window focus based on perDisplayFocusEnabled. If it is
258         // enabled then all displays have their own focus. The primary activity should have focus.
259         // If it is disabled then it should have lost the focus when the secondary activity launched
260         // on the second monitor. That brought that display to the top and removed window focus from
261         // the default display (where primary activity is running).
262         primaryActivity.waitAndAssertWindowFocusState(perDisplayFocusEnabled());
263 
264         // Both activities running on displays with their own focus should have window focus.
265         secondaryActivity.waitAndAssertWindowFocusState(true);
266         tertiaryActivity.waitAndAssertWindowFocusState(true);
267 
268         // Making the primary activity the top focus (by tapping it) will make
269         // it focused. The other two displays still have a focused window
270         tapOn(primaryActivity);
271         primaryActivity.waitAndAssertWindowFocusState(true);
272         secondaryActivity.waitAndAssertWindowFocusState(true);
273         tertiaryActivity.waitAndAssertWindowFocusState(true);
274     }
275 
276     /**
277      * Test if a display targeted by a key event can be moved to top in a single-focus system.
278      */
279     @Test
testMovingDisplayToTopByKeyEvent()280     public void testMovingDisplayToTopByKeyEvent() {
281         assumeTrue(supportsMultiDisplay());
282 
283         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
284                 DEFAULT_DISPLAY);
285         final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession();
286         final int secondaryDisplayId = session.getDisplayId();
287         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
288 
289         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, DEFAULT_DISPLAY);
290         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, INVALID_DISPLAY);
291 
292         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, secondaryDisplayId);
293         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, INVALID_DISPLAY);
294     }
295 
296     /**
297      * The display flag FLAG_STEAL_TOP_FOCUS_DISABLED prevents a display from stealing the top
298      * focus from another display. Sending targeted key events to a display usually raises that
299      * display to be the top focused display if it is not yet. If the FLAG_STEAL_TOP_FOCUS_DISABLED
300      * is set then that should not happen and the previous display stays the top focused display.
301      */
302     @Test
testStealingTopFocusDisabledDoesNotMoveDisplayToTop()303     public void testStealingTopFocusDisabledDoesNotMoveDisplayToTop() {
304         assumeTrue(supportsMultiDisplay());
305 
306         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
307                 DEFAULT_DISPLAY);
308         // Primary should have window focus for sure after launching
309         primaryActivity.waitAndAssertWindowFocusState(/* hasFocus */ true);
310         // Confirm this display has the top focus and receives untargeted events
311         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY);
312         // Confirm this display has the top focus and receives targeted events
313         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
314 
315         // Create a VirtualDisplay with top focus disabled and launch an activity on it
316         final VirtualDisplayWithOwnFocusSession session =
317                 createManagedVirtualDisplayWithOwnFocusSession(
318                         VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED);
319         final int secondaryDisplayId = session.getDisplayId();
320         // Launching the activity on the secondary display will give it window focus.
321         final SecondaryActivity secondaryActivity = session.startActivityAndFocus(
322                 SecondaryActivity.class);
323 
324         // Primary should have window focus because it still is top focused display
325         // Secondary should have window focus because it manages its own focus
326         primaryActivity.waitAndAssertWindowFocusState(/* hasFocus */ true);
327         secondaryActivity.waitAndAssertWindowFocusState(/* hasFocus */ true);
328 
329         // Confirm the default display still has top display focus
330         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_2, INVALID_DISPLAY);
331 
332         // Send a targeted key event to the secondary display.
333         // The secondary display should not get top focus because of FLAG_STEAL_TOP_FOCUS_DISABLED
334         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId);
335         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, INVALID_DISPLAY);
336 
337         // Now also check a tap does also not raise the top focus to the secondary display
338         tapOn(secondaryActivity);
339         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_5, INVALID_DISPLAY);
340 
341         // Tap the default display and check that the secondary display still has a window focus
342         tapOn(primaryActivity);
343         secondaryActivity.waitAndAssertWindowFocusState(/*hasFocus*/ true);
344         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_6, INVALID_DISPLAY);
345         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_7, secondaryDisplayId);
346     }
347 
348     /**
349      * Test if the client is notified about window-focus lost after the new focused window is drawn.
350      */
351     @Test
testDelayLosingFocus()352     public void testDelayLosingFocus() {
353         final LosingFocusActivity activity = startActivity(LosingFocusActivity.class,
354                 DEFAULT_DISPLAY);
355 
356         getInstrumentation().runOnMainSync(activity::addChildWindow);
357         activity.waitAndAssertWindowFocusState(false /* hasFocus */);
358         assertFalse("Activity must lose window focus after new focused window is drawn.",
359                 activity.losesFocusWhenNewFocusIsNotDrawn());
360     }
361 
362 
363     /**
364      * Test the following conditions:
365      * - Only the top focused window can have pointer capture.
366      * - The window which lost top-focus can be notified about pointer-capture lost.
367      */
368     @Test
testPointerCapture()369     public void testPointerCapture() {
370         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
371                 DEFAULT_DISPLAY);
372 
373         // Assert primary activity can have pointer capture before we have multiple focused windows.
374         getInstrumentation().runOnMainSync(primaryActivity::requestPointerCapture);
375         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
376 
377         assumeTrue(supportsMultiDisplay());
378         final SecondaryActivity secondaryActivity =
379                 createManagedInvisibleDisplaySession().startActivityAndFocus();
380 
381         // Assert primary activity lost pointer capture when it is not top focused.
382         primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
383 
384         // Assert secondary activity can have pointer capture when it is top focused.
385         getInstrumentation().runOnMainSync(secondaryActivity::requestPointerCapture);
386         secondaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
387 
388         tapOn(primaryActivity);
389         primaryActivity.waitAndAssertWindowFocusState(true);
390 
391         // Assert secondary activity lost pointer capture when it is not top focused.
392         secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
393     }
394 
395     /**
396      * Pointer capture could be requested after activity regains focus.
397      */
398     @DebugInputRule.DebugInput(bug = 342229227)
399     @Test
testPointerCaptureWhenFocus()400     public void testPointerCaptureWhenFocus() throws Throwable {
401         final AutoEngagePointerCaptureActivity primaryActivity =
402                 startActivity(AutoEngagePointerCaptureActivity.class, DEFAULT_DISPLAY);
403         assertTrue("Failed to reach stable window geometry",
404                 waitForStableWindowGeometry(5, TimeUnit.SECONDS));
405 
406         // Assert primary activity can have pointer capture before we have multiple focused windows.
407         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
408 
409         assumeTrue(supportsMultiDisplay());
410 
411         // This test only makes sense if `config_perDisplayFocusEnabled` is disabled.
412         assumeFalse(perDisplayFocusEnabled());
413 
414         final SecondaryActivity secondaryActivity =
415                 createManagedInvisibleDisplaySession().startActivityAndFocus();
416 
417         primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
418         // Assert primary activity lost pointer capture when it is not top focused.
419         primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
420         secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
421 
422         tapOn(primaryActivity);
423         primaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
424         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
425     }
426 
427     /**
428      * Test if the focused window can still have focus after it is moved to another display.
429      */
430     @Test
testDisplayChanged()431     public void testDisplayChanged() {
432         assumeTrue(supportsMultiDisplay());
433 
434         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
435                 DEFAULT_DISPLAY);
436 
437         final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession();
438         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
439         // Secondary display disconnected.
440         session.close();
441 
442         assertNotNull("SecondaryActivity must be started.", secondaryActivity);
443         secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY);
444         secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
445 
446         primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
447     }
448 
449     /**
450      * Ensure that a non focused display becomes focused when tapping on a focusable window on
451      * that display.
452      */
453     @Test
testTapFocusableWindow()454     public void testTapFocusableWindow() {
455         assumeTrue(supportsMultiDisplay());
456         assumeFalse(perDisplayFocusEnabled());
457 
458         PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
459         final SecondaryActivity secondaryActivity =
460                 createManagedInvisibleDisplaySession().startActivityAndFocus();
461 
462         tapOn(primaryActivity);
463         // Ensure primary activity got focus
464         primaryActivity.waitAndAssertWindowFocusState(true);
465         secondaryActivity.waitAndAssertWindowFocusState(false);
466     }
467 
468     /**
469      * Ensure that a non focused display does not become focused when tapping on a non-focusable
470      * window on that display.
471      */
472     @Test
testTapNonFocusableWindow()473     public void testTapNonFocusableWindow() throws Throwable {
474         assumeTrue(supportsMultiDisplay());
475         assumeFalse(perDisplayFocusEnabled());
476 
477         PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
478         final SecondaryActivity secondaryActivity =
479                 createManagedInvisibleDisplaySession().startActivityAndFocus();
480 
481         // Tap on a window that can't be focused and ensure that the other window in that
482         // display, primaryActivity's window, doesn't get focus.
483         getInstrumentation().runOnMainSync(() -> {
484             View view = new View(primaryActivity);
485             LayoutParams p = new LayoutParams();
486             p.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
487             primaryActivity.getWindowManager().addView(view, p);
488         });
489         assertTrue("Failed to reach stable window geometry",
490                 waitForStableWindowGeometry(5, TimeUnit.SECONDS));
491 
492         tapOn(primaryActivity);
493         // Ensure secondary activity still has focus
494         secondaryActivity.waitAndAssertWindowFocusState(true);
495         primaryActivity.waitAndAssertWindowFocusState(false);
496     }
497 
498     private static class InputTargetActivity extends FocusableActivity {
499         private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds
500         private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 3000
501                 * BuildUtils.HW_TIMEOUT_MULTIPLIER;
502         private static final long TIMEOUT_NEXT_KEY_EVENT = 1000;
503         private final Object mLockPointerCapture = new Object();
504         private final Object mLockKeyEvent = new Object();
505 
506         @GuardedBy("this")
507         private int mDisplayId = INVALID_DISPLAY;
508         @GuardedBy("mLockPointerCapture")
509         private boolean mHasPointerCapture;
510         @GuardedBy("mLockKeyEvent")
511         private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>();
512 
513         @Override
onAttachedToWindow()514         public void onAttachedToWindow() {
515             synchronized (this) {
516                 mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId();
517                 notify();
518             }
519         }
520 
521         @Override
onMovedToDisplay(int displayId, Configuration config)522         public void onMovedToDisplay(int displayId, Configuration config) {
523             synchronized (this) {
524                 mDisplayId = displayId;
525                 notify();
526             }
527         }
528 
waitAndAssertDisplayId(int displayId)529         void waitAndAssertDisplayId(int displayId) {
530             synchronized (this) {
531                 if (mDisplayId != displayId) {
532                     try {
533                         wait(TIMEOUT_DISPLAY_CHANGED);
534                     } catch (InterruptedException e) {
535                     }
536                 }
537                 assertEquals(getLogTag() + " must be moved to the display.",
538                         displayId, mDisplayId);
539             }
540         }
541 
542         @Override
onPointerCaptureChanged(boolean hasCapture)543         public void onPointerCaptureChanged(boolean hasCapture) {
544             StateLogger.logAlways(getLogTag() + " onPointerCaptureChanged: " + hasCapture);
545             synchronized (mLockPointerCapture) {
546                 mHasPointerCapture = hasCapture;
547                 mLockPointerCapture.notify();
548             }
549             super.onPointerCaptureChanged(hasCapture);
550         }
551 
waitAndAssertPointerCaptureState(boolean hasCapture)552         void waitAndAssertPointerCaptureState(boolean hasCapture) {
553             synchronized (mLockPointerCapture) {
554                 if (mHasPointerCapture != hasCapture) {
555                     try {
556                         mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED);
557                     } catch (InterruptedException e) {
558                     }
559                 }
560                 assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not")
561                         + " have pointer capture.", hasCapture, mHasPointerCapture);
562             }
563         }
564 
565         // Should be only called from the main thread.
requestPointerCapture()566         void requestPointerCapture() {
567             StateLogger.logAlways(getLogTag() + " requesting pointer capture");
568             getWindow().getDecorView().requestPointerCapture();
569         }
570 
571         @Override
dispatchKeyEvent(KeyEvent event)572         public boolean dispatchKeyEvent(KeyEvent event) {
573             synchronized (mLockKeyEvent) {
574                 mKeyEventList.add(event);
575                 mLockKeyEvent.notify();
576             }
577             return true;
578         }
579 
getKeyEventCount()580         int getKeyEventCount() {
581             synchronized (mLockKeyEvent) {
582                 return mKeyEventList.size();
583             }
584         }
585 
consumeKeyEvent(int action, int keyCode, int flags)586         private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) {
587             synchronized (mLockKeyEvent) {
588                 for (int i = mKeyEventList.size() - 1; i >= 0; i--) {
589                     final KeyEvent event = mKeyEventList.get(i);
590                     if (event.getAction() == action && event.getKeyCode() == keyCode
591                             && (event.getFlags() & flags) == flags) {
592                         mKeyEventList.remove(event);
593                         return event;
594                     }
595                 }
596             }
597             return null;
598         }
599 
assertAndConsumeKeyEvent(int action, int keyCode, int flags)600         void assertAndConsumeKeyEvent(int action, int keyCode, int flags) {
601             assertNotNull(getLogTag() + " must receive key event " + keyCodeToString(keyCode),
602                     consumeKeyEvent(action, keyCode, flags));
603         }
604 
waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)605         void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags) {
606             if (consumeKeyEvent(action, keyCode, flags) == null) {
607                 synchronized (mLockKeyEvent) {
608                     try {
609                         mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT);
610                     } catch (InterruptedException e) {
611                     }
612                 }
613                 assertAndConsumeKeyEvent(action, keyCode, flags);
614             }
615         }
616     }
617 
618     public static class PrimaryActivity extends InputTargetActivity { }
619 
620     public static class SecondaryActivity extends InputTargetActivity { }
621 
622     public static class TertiaryActivity extends InputTargetActivity { }
623 
624     public static class LosingFocusActivity extends InputTargetActivity {
625         private boolean mChildWindowHasDrawn = false;
626 
627         @GuardedBy("this")
628         private boolean mLosesFocusWhenNewFocusIsNotDrawn = false;
629 
addChildWindow()630         void addChildWindow() {
631             getWindowManager().addView(new View(this) {
632                 @Override
633                 protected void onDraw(Canvas canvas) {
634                     mChildWindowHasDrawn = true;
635                 }
636             }, new LayoutParams());
637         }
638 
639         @Override
onWindowFocusChanged(boolean hasFocus)640         public void onWindowFocusChanged(boolean hasFocus) {
641             if (!hasFocus && !mChildWindowHasDrawn) {
642                 synchronized (this) {
643                     mLosesFocusWhenNewFocusIsNotDrawn = true;
644                 }
645             }
646             super.onWindowFocusChanged(hasFocus);
647         }
648 
losesFocusWhenNewFocusIsNotDrawn()649         boolean losesFocusWhenNewFocusIsNotDrawn() {
650             synchronized (this) {
651                 return mLosesFocusWhenNewFocusIsNotDrawn;
652             }
653         }
654     }
655 
656     public static class AutoEngagePointerCaptureActivity extends InputTargetActivity {
657         @Override
onWindowFocusChanged(boolean hasFocus)658         public void onWindowFocusChanged(boolean hasFocus) {
659             if (hasFocus) {
660                 requestPointerCapture();
661             }
662             super.onWindowFocusChanged(hasFocus);
663         }
664     }
665 
createManagedInvisibleDisplaySession()666     private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() {
667         return mObjectTracker.manage(
668                 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext()));
669     }
670 
671     /** An untrusted virtual display that won't show on default screen. */
672     private static class InvisibleVirtualDisplaySession implements AutoCloseable {
673         private static final int WIDTH = 800;
674         private static final int HEIGHT = 480;
675         private static final int DENSITY = 160;
676 
677         private final VirtualDisplay mVirtualDisplay;
678         private final ImageReader mReader;
679         private final Display mDisplay;
680 
InvisibleVirtualDisplaySession(Context context)681         InvisibleVirtualDisplaySession(Context context) {
682             mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888,
683                     2 /* maxImages */);
684             mVirtualDisplay = context.getSystemService(DisplayManager.class)
685                     .createVirtualDisplay(WindowFocusTests.class.getSimpleName(),
686                             WIDTH, HEIGHT, DENSITY, mReader.getSurface(),
687                             VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
688             mDisplay = mVirtualDisplay.getDisplay();
689         }
690 
getDisplayId()691         int getDisplayId() {
692             return mDisplay.getDisplayId();
693         }
694 
startActivityAndFocus()695         SecondaryActivity startActivityAndFocus() {
696             return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */,
697                     SecondaryActivity.class);
698         }
699 
700         @Override
close()701         public void close() {
702             if (mVirtualDisplay != null) {
703                 mVirtualDisplay.release();
704             }
705             if (mReader != null) {
706                 mReader.close();
707             }
708         }
709     }
710 
createManagedVirtualDisplayWithOwnFocusSession()711     private VirtualDisplayWithOwnFocusSession createManagedVirtualDisplayWithOwnFocusSession() {
712         return createManagedVirtualDisplayWithOwnFocusSession(/* additionalFlags= */ 0);
713     }
714 
createManagedVirtualDisplayWithOwnFocusSession( int additionalFlags)715     private VirtualDisplayWithOwnFocusSession createManagedVirtualDisplayWithOwnFocusSession(
716             int additionalFlags) {
717         return mObjectTracker.manage(
718                 new VirtualDisplayWithOwnFocusSession(getInstrumentation().getTargetContext(),
719                         additionalFlags));
720     }
721 
722     /** A trusted virtual display that has its own focus and touch mode states. */
723     private static class VirtualDisplayWithOwnFocusSession implements AutoCloseable {
724         private static final int WIDTH = 800;
725         private static final int HEIGHT = 480;
726         private static final int DENSITY = 160;
727 
728         private VirtualDisplay mVirtualDisplay;
729         private final ImageReader mReader;
730         private final Display mDisplay;
731 
732         /**
733          * @param context         The context, used to get the DisplayManager.
734          * @param additionalFlags Additional VirtualDisplayFlag to add. See
735          *                        {@link #getVirtualDisplayFlags()} for the default flags that are
736          *                        set.
737          */
VirtualDisplayWithOwnFocusSession(Context context, int additionalFlags)738         VirtualDisplayWithOwnFocusSession(Context context, int additionalFlags) {
739             mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888,
740                     /* maxImages= */ 2);
741             SystemUtil.runWithShellPermissionIdentity(() -> {
742                 mVirtualDisplay = context.getSystemService(DisplayManager.class)
743                         .createVirtualDisplay(WindowFocusTests.class.getSimpleName(), WIDTH, HEIGHT,
744                                 DENSITY, mReader.getSurface(),
745                                 getVirtualDisplayFlags() | additionalFlags);
746             });
747             mDisplay = mVirtualDisplay.getDisplay();
748         }
749 
750         /**
751          * @return Get the default VirtualDisplayFlags to set for the creation of the VirtualDisplay
752          */
getVirtualDisplayFlags()753         int getVirtualDisplayFlags() {
754             return VIRTUAL_DISPLAY_FLAG_PUBLIC
755                     | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
756                     | VIRTUAL_DISPLAY_FLAG_TRUSTED
757                     | VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
758         }
759 
getDisplayId()760         int getDisplayId() {
761             return mDisplay.getDisplayId();
762         }
763 
startActivityAndFocus(Class<T> cls)764         <T extends InputTargetActivity> T startActivityAndFocus(Class<T> cls) {
765             return WindowFocusTests.startActivityAndFocus(getDisplayId(), /* hasFocus= */ true,
766                     cls);
767         }
768 
769         @Override
close()770         public void close() {
771             if (mVirtualDisplay != null) {
772                 mVirtualDisplay.release();
773             }
774             if (mReader != null) {
775                 mReader.close();
776             }
777         }
778     }
779 
createManagedSimulatedDisplaySession()780     private SimulatedDisplaySession createManagedSimulatedDisplaySession() {
781         return mObjectTracker.manage(new SimulatedDisplaySession());
782     }
783 
784     private class SimulatedDisplaySession implements AutoCloseable {
785         private final VirtualDisplaySession mVirtualDisplaySession;
786         private final WindowManagerState.DisplayContent mVirtualDisplay;
787 
SimulatedDisplaySession()788         SimulatedDisplaySession() {
789             mVirtualDisplaySession = new VirtualDisplaySession();
790             mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay();
791         }
792 
getDisplayId()793         int getDisplayId() {
794             return mVirtualDisplay.mId;
795         }
796 
startActivityAndFocus()797         SecondaryActivity startActivityAndFocus() {
798             return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */,
799                     SecondaryActivity.class);
800         }
801 
802         @Override
close()803         public void close() {
804             mVirtualDisplaySession.close();
805         }
806     }
807 
startActivityAndFocus(int displayId, boolean hasFocus, Class<T> cls)808     private static <T extends InputTargetActivity> T startActivityAndFocus(int displayId,
809             boolean hasFocus, Class<T> cls) {
810         // An untrusted virtual display won't have focus until the display is touched.
811         final T activity = WindowManagerTestBase.startActivity(
812                 cls, displayId, hasFocus);
813         tapOn(activity);
814         activity.waitAndAssertWindowFocusState(true);
815         return activity;
816     }
817 }
818