• 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_PUBLIC;
21 import static android.view.Display.DEFAULT_DISPLAY;
22 import static android.view.Display.INVALID_DISPLAY;
23 import static android.view.KeyEvent.ACTION_DOWN;
24 import static android.view.KeyEvent.ACTION_UP;
25 import static android.view.KeyEvent.FLAG_CANCELED;
26 import static android.view.KeyEvent.KEYCODE_0;
27 import static android.view.KeyEvent.KEYCODE_1;
28 import static android.view.KeyEvent.KEYCODE_2;
29 import static android.view.KeyEvent.KEYCODE_3;
30 import static android.view.KeyEvent.KEYCODE_4;
31 import static android.view.KeyEvent.KEYCODE_5;
32 import static android.view.KeyEvent.KEYCODE_6;
33 import static android.view.KeyEvent.KEYCODE_7;
34 import static android.view.KeyEvent.KEYCODE_8;
35 import static android.view.KeyEvent.keyCodeToString;
36 
37 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
38 
39 import static org.junit.Assert.assertEquals;
40 import static org.junit.Assert.assertFalse;
41 import static org.junit.Assert.assertNotNull;
42 import static org.junit.Assume.assumeFalse;
43 import static org.junit.Assume.assumeTrue;
44 
45 import android.app.Activity;
46 import android.content.Context;
47 import android.content.res.Configuration;
48 import android.graphics.Canvas;
49 import android.graphics.PixelFormat;
50 import android.graphics.Point;
51 import android.hardware.display.DisplayManager;
52 import android.hardware.display.VirtualDisplay;
53 import android.media.ImageReader;
54 import android.os.SystemClock;
55 import android.platform.test.annotations.Presubmit;
56 import android.view.Display;
57 import android.view.KeyEvent;
58 import android.view.MotionEvent;
59 import android.view.View;
60 import android.view.WindowManager.LayoutParams;
61 
62 import androidx.annotation.NonNull;
63 
64 import org.junit.Test;
65 
66 import java.util.ArrayList;
67 
68 import javax.annotation.concurrent.GuardedBy;
69 
70 /**
71  * Ensure window focus assignment is executed as expected.
72  *
73  * Build/Install/Run:
74  *     atest WindowFocusTests
75  */
76 @Presubmit
77 public class WindowFocusTests extends WindowManagerTestBase {
78 
sendKey(int action, int keyCode, int displayId)79     private static void sendKey(int action, int keyCode, int displayId) {
80         final KeyEvent keyEvent = new KeyEvent(action, keyCode);
81         keyEvent.setDisplayId(displayId);
82         getInstrumentation().sendKeySync(keyEvent);
83     }
84 
sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode, int targetDisplayId)85     private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode,
86             int targetDisplayId) {
87         sendAndAssertTargetConsumedKey(target, ACTION_DOWN, keyCode, targetDisplayId);
88         sendAndAssertTargetConsumedKey(target, ACTION_UP, keyCode, targetDisplayId);
89     }
90 
sendAndAssertTargetConsumedKey(InputTargetActivity target, int action, int keyCode, int targetDisplayId)91     private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int action,
92             int keyCode, int targetDisplayId) {
93         final int eventCount = target.getKeyEventCount();
94         sendKey(action, keyCode, targetDisplayId);
95         target.assertAndConsumeKeyEvent(action, keyCode, 0 /* flags */);
96         assertEquals(target.getLogTag() + " must only receive key event sent.", eventCount,
97                 target.getKeyEventCount());
98     }
99 
tapOn(@onNull Activity activity)100     private static void tapOn(@NonNull Activity activity) {
101         final Point p = getCenterOfActivityOnScreen(activity);
102         final int displayId = activity.getDisplayId();
103 
104         final long downTime = SystemClock.elapsedRealtime();
105         final MotionEvent downEvent = MotionEvent.obtain(downTime, downTime,
106                 MotionEvent.ACTION_DOWN, p.x, p.y, 0 /* metaState */);
107         downEvent.setDisplayId(displayId);
108         getInstrumentation().sendPointerSync(downEvent);
109         final MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.elapsedRealtime(),
110                 MotionEvent.ACTION_UP, p.x, p.y, 0 /* metaState */);
111         upEvent.setDisplayId(displayId);
112         getInstrumentation().sendPointerSync(upEvent);
113     }
114 
getCenterOfActivityOnScreen(@onNull Activity activity)115     private static Point getCenterOfActivityOnScreen(@NonNull Activity activity) {
116         final View decorView = activity.getWindow().getDecorView();
117         final int[] location = new int[2];
118         decorView.getLocationOnScreen(location);
119         return new Point(location[0] + decorView.getWidth() / 2,
120                 location[1] + decorView.getHeight() / 2);
121     }
122 
123     /**
124      * Test the following conditions:
125      * - Each display can have a focused window at the same time.
126      * - Focused windows can receive display-specified key events.
127      * - The top focused window can receive display-unspecified key events.
128      * - Taping on a display will make the focused window on it become top-focused.
129      * - The window which lost top-focus can receive display-unspecified cancel events.
130      */
131     @Test
testKeyReceiving()132     public void testKeyReceiving() {
133         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
134                 DEFAULT_DISPLAY);
135         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY);
136         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
137 
138         assumeTrue(supportsMultiDisplay());
139 
140         // VirtualDisplay can't maintain perDisplayFocus because it is not trusted,
141         // so uses SimulatedDisplay instead.
142         final SimulatedDisplaySession session = createManagedSimulatedDisplaySession();
143         final int secondaryDisplayId = session.getDisplayId();
144         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
145         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
146         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId);
147 
148         final boolean perDisplayFocusEnabled = perDisplayFocusEnabled();
149         if (perDisplayFocusEnabled) {
150             primaryActivity.assertWindowFocusState(true /* hasFocus */);
151             sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, DEFAULT_DISPLAY);
152         } else {
153             primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
154         }
155 
156         // Press display-unspecified keys and a display-specified key but not release them.
157         sendKey(ACTION_DOWN, KEYCODE_5, INVALID_DISPLAY);
158         sendKey(ACTION_DOWN, KEYCODE_6, secondaryDisplayId);
159         sendKey(ACTION_DOWN, KEYCODE_7, INVALID_DISPLAY);
160         secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_5, 0 /* flags */);
161         secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_6, 0 /* flags */);
162         secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_7, 0 /* flags */);
163 
164         tapOn(primaryActivity);
165 
166         // Assert only display-unspecified key would be cancelled after secondary activity is
167         // not top focused if per-display focus is enabled. Otherwise, assert all non-released
168         // key events sent to secondary activity would be cancelled.
169         secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_5, FLAG_CANCELED);
170         secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_7, FLAG_CANCELED);
171         if (!perDisplayFocusEnabled) {
172             secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
173         }
174         assertEquals(secondaryActivity.getLogTag() + " must only receive expected events.",
175                 0 /* expected event count */, secondaryActivity.getKeyEventCount());
176 
177         // Assert primary activity become top focused after tapping on default display.
178         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_8, INVALID_DISPLAY);
179     }
180 
181     /**
182      * Test if a display targeted by a key event can be moved to top in a single-focus system.
183      */
184     @Test
testMovingDisplayToTopByKeyEvent()185     public void testMovingDisplayToTopByKeyEvent() {
186         assumeTrue(supportsMultiDisplay());
187         assumeFalse(perDisplayFocusEnabled());
188 
189         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
190                 DEFAULT_DISPLAY);
191         final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession();
192         final int secondaryDisplayId = session.getDisplayId();
193         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
194 
195         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, DEFAULT_DISPLAY);
196         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, INVALID_DISPLAY);
197 
198         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, secondaryDisplayId);
199         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, INVALID_DISPLAY);
200     }
201 
202     /**
203      * Test if the client is notified about window-focus lost after the new focused window is drawn.
204      */
205     @Test
testDelayLosingFocus()206     public void testDelayLosingFocus() {
207         final LosingFocusActivity activity = startActivity(LosingFocusActivity.class,
208                 DEFAULT_DISPLAY);
209 
210         getInstrumentation().runOnMainSync(activity::addChildWindow);
211         activity.waitAndAssertWindowFocusState(false /* hasFocus */);
212         assertFalse("Activity must lose window focus after new focused window is drawn.",
213                 activity.losesFocusWhenNewFocusIsNotDrawn());
214     }
215 
216 
217     /**
218      * Test the following conditions:
219      * - Only the top focused window can have pointer capture.
220      * - The window which lost top-focus can be notified about pointer-capture lost.
221      */
222     @Test
testPointerCapture()223     public void testPointerCapture() {
224         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
225                 DEFAULT_DISPLAY);
226 
227         // Assert primary activity can have pointer capture before we have multiple focused windows.
228         getInstrumentation().runOnMainSync(primaryActivity::requestPointerCapture);
229         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
230 
231         assumeTrue(supportsMultiDisplay());
232         final SecondaryActivity secondaryActivity =
233                 createManagedInvisibleDisplaySession().startActivityAndFocus();
234 
235         // Assert primary activity lost pointer capture when it is not top focused.
236         primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
237 
238         // Assert secondary activity can have pointer capture when it is top focused.
239         getInstrumentation().runOnMainSync(secondaryActivity::requestPointerCapture);
240         secondaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
241 
242         tapOn(primaryActivity);
243         primaryActivity.waitAndAssertWindowFocusState(true);
244 
245         // Assert secondary activity lost pointer capture when it is not top focused.
246         secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
247     }
248 
249     /**
250      * Pointer capture could be requested after activity regains focus.
251      */
252     @Test
testPointerCaptureWhenFocus()253     public void testPointerCaptureWhenFocus() {
254         final AutoEngagePointerCaptureActivity primaryActivity =
255                 startActivity(AutoEngagePointerCaptureActivity.class, DEFAULT_DISPLAY);
256 
257         // Assert primary activity can have pointer capture before we have multiple focused windows.
258         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
259 
260         assumeTrue(supportsMultiDisplay());
261 
262         // This test only makes sense if `config_perDisplayFocusEnabled` is disabled.
263         assumeFalse(perDisplayFocusEnabled());
264 
265         final SecondaryActivity secondaryActivity =
266                 createManagedInvisibleDisplaySession().startActivityAndFocus();
267 
268         primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
269         // Assert primary activity lost pointer capture when it is not top focused.
270         primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
271         secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
272 
273         tapOn(primaryActivity);
274         primaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
275         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
276     }
277 
278     /**
279      * Test if the focused window can still have focus after it is moved to another display.
280      */
281     @Test
testDisplayChanged()282     public void testDisplayChanged() {
283         assumeTrue(supportsMultiDisplay());
284 
285         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
286                 DEFAULT_DISPLAY);
287 
288         final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession();
289         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
290         // Secondary display disconnected.
291         session.close();
292 
293         assertNotNull("SecondaryActivity must be started.", secondaryActivity);
294         secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY);
295         secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
296 
297         primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
298     }
299 
300     /**
301      * Ensure that a non focused display becomes focused when tapping on a focusable window on
302      * that display.
303      */
304     @Test
testTapFocusableWindow()305     public void testTapFocusableWindow() {
306         assumeTrue(supportsMultiDisplay());
307         assumeFalse(perDisplayFocusEnabled());
308 
309         PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
310         final SecondaryActivity secondaryActivity =
311                 createManagedInvisibleDisplaySession().startActivityAndFocus();
312 
313         tapOn(primaryActivity);
314         // Ensure primary activity got focus
315         primaryActivity.waitAndAssertWindowFocusState(true);
316         secondaryActivity.waitAndAssertWindowFocusState(false);
317     }
318 
319     /**
320      * Ensure that a non focused display does not become focused when tapping on a non-focusable
321      * window on that display.
322      */
323     @Test
testTapNonFocusableWindow()324     public void testTapNonFocusableWindow() {
325         assumeTrue(supportsMultiDisplay());
326         assumeFalse(perDisplayFocusEnabled());
327 
328         PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
329         final SecondaryActivity secondaryActivity =
330                 createManagedInvisibleDisplaySession().startActivityAndFocus();
331 
332         // Tap on a window that can't be focused and ensure that the other window in that
333         // display, primaryActivity's window, doesn't get focus.
334         getInstrumentation().runOnMainSync(() -> {
335             View view = new View(primaryActivity);
336             LayoutParams p = new LayoutParams();
337             p.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
338             primaryActivity.getWindowManager().addView(view, p);
339         });
340         getInstrumentation().waitForIdleSync();
341 
342         tapOn(primaryActivity);
343         // Ensure secondary activity still has focus
344         secondaryActivity.waitAndAssertWindowFocusState(true);
345         primaryActivity.waitAndAssertWindowFocusState(false);
346     }
347 
348     private static class InputTargetActivity extends FocusableActivity {
349         private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds
350         private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000;
351         private static final long TIMEOUT_NEXT_KEY_EVENT = 1000;
352 
353         private final Object mLockPointerCapture = new Object();
354         private final Object mLockKeyEvent = new Object();
355 
356         @GuardedBy("this")
357         private int mDisplayId = INVALID_DISPLAY;
358         @GuardedBy("mLockPointerCapture")
359         private boolean mHasPointerCapture;
360         @GuardedBy("mLockKeyEvent")
361         private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>();
362 
363         @Override
onAttachedToWindow()364         public void onAttachedToWindow() {
365             synchronized (this) {
366                 mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId();
367                 notify();
368             }
369         }
370 
371         @Override
onMovedToDisplay(int displayId, Configuration config)372         public void onMovedToDisplay(int displayId, Configuration config) {
373             synchronized (this) {
374                 mDisplayId = displayId;
375                 notify();
376             }
377         }
378 
waitAndAssertDisplayId(int displayId)379         void waitAndAssertDisplayId(int displayId) {
380             synchronized (this) {
381                 if (mDisplayId != displayId) {
382                     try {
383                         wait(TIMEOUT_DISPLAY_CHANGED);
384                     } catch (InterruptedException e) {
385                     }
386                 }
387                 assertEquals(getLogTag() + " must be moved to the display.",
388                         displayId, mDisplayId);
389             }
390         }
391 
392         @Override
onPointerCaptureChanged(boolean hasCapture)393         public void onPointerCaptureChanged(boolean hasCapture) {
394             synchronized (mLockPointerCapture) {
395                 mHasPointerCapture = hasCapture;
396                 mLockPointerCapture.notify();
397             }
398         }
399 
waitAndAssertPointerCaptureState(boolean hasCapture)400         void waitAndAssertPointerCaptureState(boolean hasCapture) {
401             synchronized (mLockPointerCapture) {
402                 if (mHasPointerCapture != hasCapture) {
403                     try {
404                         mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED);
405                     } catch (InterruptedException e) {
406                     }
407                 }
408                 assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not")
409                         + " have pointer capture.", hasCapture, mHasPointerCapture);
410             }
411         }
412 
413         // Should be only called from the main thread.
requestPointerCapture()414         void requestPointerCapture() {
415             getWindow().getDecorView().requestPointerCapture();
416         }
417 
418         @Override
dispatchKeyEvent(KeyEvent event)419         public boolean dispatchKeyEvent(KeyEvent event) {
420             synchronized (mLockKeyEvent) {
421                 mKeyEventList.add(event);
422                 mLockKeyEvent.notify();
423             }
424             return true;
425         }
426 
getKeyEventCount()427         int getKeyEventCount() {
428             synchronized (mLockKeyEvent) {
429                 return mKeyEventList.size();
430             }
431         }
432 
consumeKeyEvent(int action, int keyCode, int flags)433         private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) {
434             synchronized (mLockKeyEvent) {
435                 for (int i = mKeyEventList.size() - 1; i >= 0; i--) {
436                     final KeyEvent event = mKeyEventList.get(i);
437                     if (event.getAction() == action && event.getKeyCode() == keyCode
438                             && (event.getFlags() & flags) == flags) {
439                         mKeyEventList.remove(event);
440                         return event;
441                     }
442                 }
443             }
444             return null;
445         }
446 
assertAndConsumeKeyEvent(int action, int keyCode, int flags)447         void assertAndConsumeKeyEvent(int action, int keyCode, int flags) {
448             assertNotNull(getLogTag() + " must receive key event " + keyCodeToString(keyCode),
449                     consumeKeyEvent(action, keyCode, flags));
450         }
451 
waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)452         void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags) {
453             if (consumeKeyEvent(action, keyCode, flags) == null) {
454                 synchronized (mLockKeyEvent) {
455                     try {
456                         mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT);
457                     } catch (InterruptedException e) {
458                     }
459                 }
460                 assertAndConsumeKeyEvent(action, keyCode, flags);
461             }
462         }
463     }
464 
465     public static class PrimaryActivity extends InputTargetActivity { }
466 
467     public static class SecondaryActivity extends InputTargetActivity { }
468 
469     public static class LosingFocusActivity extends InputTargetActivity {
470         private boolean mChildWindowHasDrawn = false;
471 
472         @GuardedBy("this")
473         private boolean mLosesFocusWhenNewFocusIsNotDrawn = false;
474 
addChildWindow()475         void addChildWindow() {
476             getWindowManager().addView(new View(this) {
477                 @Override
478                 protected void onDraw(Canvas canvas) {
479                     mChildWindowHasDrawn = true;
480                 }
481             }, new LayoutParams());
482         }
483 
484         @Override
onWindowFocusChanged(boolean hasFocus)485         public void onWindowFocusChanged(boolean hasFocus) {
486             if (!hasFocus && !mChildWindowHasDrawn) {
487                 synchronized (this) {
488                     mLosesFocusWhenNewFocusIsNotDrawn = true;
489                 }
490             }
491             super.onWindowFocusChanged(hasFocus);
492         }
493 
losesFocusWhenNewFocusIsNotDrawn()494         boolean losesFocusWhenNewFocusIsNotDrawn() {
495             synchronized (this) {
496                 return mLosesFocusWhenNewFocusIsNotDrawn;
497             }
498         }
499     }
500 
501     public static class AutoEngagePointerCaptureActivity extends InputTargetActivity {
502         @Override
onWindowFocusChanged(boolean hasFocus)503         public void onWindowFocusChanged(boolean hasFocus) {
504             if (hasFocus) {
505                 requestPointerCapture();
506             }
507             super.onWindowFocusChanged(hasFocus);
508         }
509     }
510 
createManagedInvisibleDisplaySession()511     private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() {
512         return mObjectTracker.manage(
513                 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext()));
514     }
515 
516     /** An untrusted virtual display that won't show on default screen. */
517     private static class InvisibleVirtualDisplaySession implements AutoCloseable {
518         private static final int WIDTH = 800;
519         private static final int HEIGHT = 480;
520         private static final int DENSITY = 160;
521 
522         private final VirtualDisplay mVirtualDisplay;
523         private final ImageReader mReader;
524         private final Display mDisplay;
525 
InvisibleVirtualDisplaySession(Context context)526         InvisibleVirtualDisplaySession(Context context) {
527             mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888,
528                     2 /* maxImages */);
529             mVirtualDisplay = context.getSystemService(DisplayManager.class)
530                     .createVirtualDisplay(WindowFocusTests.class.getSimpleName(),
531                             WIDTH, HEIGHT, DENSITY, mReader.getSurface(),
532                             VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
533             mDisplay = mVirtualDisplay.getDisplay();
534         }
535 
getDisplayId()536         int getDisplayId() {
537             return mDisplay.getDisplayId();
538         }
539 
startActivityAndFocus()540         SecondaryActivity startActivityAndFocus() {
541             return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */);
542         }
543 
544         @Override
close()545         public void close() {
546             if (mVirtualDisplay != null) {
547                 mVirtualDisplay.release();
548             }
549             if (mReader != null) {
550                 mReader.close();
551             }
552         }
553     }
554 
createManagedSimulatedDisplaySession()555     private SimulatedDisplaySession createManagedSimulatedDisplaySession() {
556         return mObjectTracker.manage(new SimulatedDisplaySession());
557     }
558 
559     private class SimulatedDisplaySession implements AutoCloseable {
560         private final VirtualDisplaySession mVirtualDisplaySession;
561         private final WindowManagerState.DisplayContent mVirtualDisplay;
562 
SimulatedDisplaySession()563         SimulatedDisplaySession() {
564             mVirtualDisplaySession = new VirtualDisplaySession();
565             mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay();
566         }
567 
getDisplayId()568         int getDisplayId() {
569             return mVirtualDisplay.mId;
570         }
571 
startActivityAndFocus()572         SecondaryActivity startActivityAndFocus() {
573             return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */);
574         }
575 
576         @Override
close()577         public void close() {
578             mVirtualDisplaySession.close();
579         }
580     }
581 
startActivityAndFocus(int displayId, boolean hasFocus)582     private static SecondaryActivity startActivityAndFocus(int displayId, boolean hasFocus) {
583         // An untrusted virtual display won't have focus until the display is touched.
584         final SecondaryActivity activity = WindowManagerTestBase.startActivity(
585                 SecondaryActivity.class, displayId, hasFocus);
586         tapOn(activity);
587         activity.waitAndAssertWindowFocusState(true);
588         return activity;
589     }
590 }
591