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