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