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 SecondaryActivity secondaryActivity = session.startActivityAndFocus();
294         // Secondary display disconnected.
295         session.close();
296 
297         assertNotNull("SecondaryActivity must be started.", secondaryActivity);
298         secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY);
299         secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
300 
301         primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
302     }
303 
304     /**
305      * Ensure that a non focused display becomes focused when tapping on a focusable window on
306      * that display.
307      */
308     @Test
testTapFocusableWindow()309     public void testTapFocusableWindow() {
310         assumeTrue(supportsMultiDisplay());
311         assumeFalse(perDisplayFocusEnabled());
312 
313         PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
314         final SecondaryActivity secondaryActivity =
315                 createManagedInvisibleDisplaySession().startActivityAndFocus();
316 
317         tapOn(primaryActivity);
318         // Ensure primary activity got focus
319         primaryActivity.waitAndAssertWindowFocusState(true);
320         secondaryActivity.waitAndAssertWindowFocusState(false);
321     }
322 
323     /**
324      * Ensure that a non focused display does not become focused when tapping on a non-focusable
325      * window on that display.
326      */
327     @Test
testTapNonFocusableWindow()328     public void testTapNonFocusableWindow() {
329         assumeTrue(supportsMultiDisplay());
330         assumeFalse(perDisplayFocusEnabled());
331 
332         PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
333         final SecondaryActivity secondaryActivity =
334                 createManagedInvisibleDisplaySession().startActivityAndFocus();
335 
336         // Tap on a window that can't be focused and ensure that the other window in that
337         // display, primaryActivity's window, doesn't get focus.
338         getInstrumentation().runOnMainSync(() -> {
339             View view = new View(primaryActivity);
340             LayoutParams p = new LayoutParams();
341             p.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
342             primaryActivity.getWindowManager().addView(view, p);
343         });
344         getInstrumentation().waitForIdleSync();
345 
346         tapOn(primaryActivity);
347         // Ensure secondary activity still has focus
348         secondaryActivity.waitAndAssertWindowFocusState(true);
349         primaryActivity.waitAndAssertWindowFocusState(false);
350     }
351 
352     private static class InputTargetActivity extends FocusableActivity {
353         private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds
354         private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000;
355         private static final long TIMEOUT_NEXT_KEY_EVENT = 1000;
356 
357         private final Object mLockPointerCapture = new Object();
358         private final Object mLockKeyEvent = new Object();
359 
360         @GuardedBy("this")
361         private int mDisplayId = INVALID_DISPLAY;
362         @GuardedBy("mLockPointerCapture")
363         private boolean mHasPointerCapture;
364         @GuardedBy("mLockKeyEvent")
365         private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>();
366 
367         @Override
onAttachedToWindow()368         public void onAttachedToWindow() {
369             synchronized (this) {
370                 mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId();
371                 notify();
372             }
373         }
374 
375         @Override
onMovedToDisplay(int displayId, Configuration config)376         public void onMovedToDisplay(int displayId, Configuration config) {
377             synchronized (this) {
378                 mDisplayId = displayId;
379                 notify();
380             }
381         }
382 
waitAndAssertDisplayId(int displayId)383         void waitAndAssertDisplayId(int displayId) {
384             synchronized (this) {
385                 if (mDisplayId != displayId) {
386                     try {
387                         wait(TIMEOUT_DISPLAY_CHANGED);
388                     } catch (InterruptedException e) {
389                     }
390                 }
391                 assertEquals(getLogTag() + " must be moved to the display.",
392                         displayId, mDisplayId);
393             }
394         }
395 
396         @Override
onPointerCaptureChanged(boolean hasCapture)397         public void onPointerCaptureChanged(boolean hasCapture) {
398             synchronized (mLockPointerCapture) {
399                 mHasPointerCapture = hasCapture;
400                 mLockPointerCapture.notify();
401             }
402         }
403 
waitAndAssertPointerCaptureState(boolean hasCapture)404         void waitAndAssertPointerCaptureState(boolean hasCapture) {
405             synchronized (mLockPointerCapture) {
406                 if (mHasPointerCapture != hasCapture) {
407                     try {
408                         mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED);
409                     } catch (InterruptedException e) {
410                     }
411                 }
412                 assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not")
413                         + " have pointer capture.", hasCapture, mHasPointerCapture);
414             }
415         }
416 
417         // Should be only called from the main thread.
requestPointerCapture()418         void requestPointerCapture() {
419             getWindow().getDecorView().requestPointerCapture();
420         }
421 
422         @Override
dispatchKeyEvent(KeyEvent event)423         public boolean dispatchKeyEvent(KeyEvent event) {
424             synchronized (mLockKeyEvent) {
425                 mKeyEventList.add(event);
426                 mLockKeyEvent.notify();
427             }
428             return true;
429         }
430 
getKeyEventCount()431         int getKeyEventCount() {
432             synchronized (mLockKeyEvent) {
433                 return mKeyEventList.size();
434             }
435         }
436 
consumeKeyEvent(int action, int keyCode, int flags)437         private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) {
438             synchronized (mLockKeyEvent) {
439                 for (int i = mKeyEventList.size() - 1; i >= 0; i--) {
440                     final KeyEvent event = mKeyEventList.get(i);
441                     if (event.getAction() == action && event.getKeyCode() == keyCode
442                             && (event.getFlags() & flags) == flags) {
443                         mKeyEventList.remove(event);
444                         return event;
445                     }
446                 }
447             }
448             return null;
449         }
450 
assertAndConsumeKeyEvent(int action, int keyCode, int flags)451         void assertAndConsumeKeyEvent(int action, int keyCode, int flags) {
452             assertNotNull(getLogTag() + " must receive key event " + keyCodeToString(keyCode),
453                     consumeKeyEvent(action, keyCode, flags));
454         }
455 
waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)456         void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags) {
457             if (consumeKeyEvent(action, keyCode, flags) == null) {
458                 synchronized (mLockKeyEvent) {
459                     try {
460                         mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT);
461                     } catch (InterruptedException e) {
462                     }
463                 }
464                 assertAndConsumeKeyEvent(action, keyCode, flags);
465             }
466         }
467     }
468 
469     public static class PrimaryActivity extends InputTargetActivity { }
470 
471     public static class SecondaryActivity extends InputTargetActivity { }
472 
473     public static class LosingFocusActivity extends InputTargetActivity {
474         private boolean mChildWindowHasDrawn = false;
475 
476         @GuardedBy("this")
477         private boolean mLosesFocusWhenNewFocusIsNotDrawn = false;
478 
addChildWindow()479         void addChildWindow() {
480             getWindowManager().addView(new View(this) {
481                 @Override
482                 protected void onDraw(Canvas canvas) {
483                     mChildWindowHasDrawn = true;
484                 }
485             }, new LayoutParams());
486         }
487 
488         @Override
onWindowFocusChanged(boolean hasFocus)489         public void onWindowFocusChanged(boolean hasFocus) {
490             if (!hasFocus && !mChildWindowHasDrawn) {
491                 synchronized (this) {
492                     mLosesFocusWhenNewFocusIsNotDrawn = true;
493                 }
494             }
495             super.onWindowFocusChanged(hasFocus);
496         }
497 
losesFocusWhenNewFocusIsNotDrawn()498         boolean losesFocusWhenNewFocusIsNotDrawn() {
499             synchronized (this) {
500                 return mLosesFocusWhenNewFocusIsNotDrawn;
501             }
502         }
503     }
504 
505     public static class AutoEngagePointerCaptureActivity extends InputTargetActivity {
506         @Override
onWindowFocusChanged(boolean hasFocus)507         public void onWindowFocusChanged(boolean hasFocus) {
508             if (hasFocus) {
509                 requestPointerCapture();
510             }
511             super.onWindowFocusChanged(hasFocus);
512         }
513     }
514 
createManagedInvisibleDisplaySession()515     private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() {
516         return mObjectTracker.manage(
517                 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext()));
518     }
519 
520     /** An untrusted virtual display that won't show on default screen. */
521     private static class InvisibleVirtualDisplaySession implements AutoCloseable {
522         private static final int WIDTH = 800;
523         private static final int HEIGHT = 480;
524         private static final int DENSITY = 160;
525 
526         private final VirtualDisplay mVirtualDisplay;
527         private final ImageReader mReader;
528         private final Display mDisplay;
529 
InvisibleVirtualDisplaySession(Context context)530         InvisibleVirtualDisplaySession(Context context) {
531             mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888,
532                     2 /* maxImages */);
533             mVirtualDisplay = context.getSystemService(DisplayManager.class)
534                     .createVirtualDisplay(WindowFocusTests.class.getSimpleName(),
535                             WIDTH, HEIGHT, DENSITY, mReader.getSurface(),
536                             VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
537             mDisplay = mVirtualDisplay.getDisplay();
538         }
539 
getDisplayId()540         int getDisplayId() {
541             return mDisplay.getDisplayId();
542         }
543 
startActivityAndFocus()544         SecondaryActivity startActivityAndFocus() {
545             return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */);
546         }
547 
548         @Override
close()549         public void close() {
550             if (mVirtualDisplay != null) {
551                 mVirtualDisplay.release();
552             }
553             if (mReader != null) {
554                 mReader.close();
555             }
556         }
557     }
558 
createManagedSimulatedDisplaySession()559     private SimulatedDisplaySession createManagedSimulatedDisplaySession() {
560         return mObjectTracker.manage(new SimulatedDisplaySession());
561     }
562 
563     private class SimulatedDisplaySession implements AutoCloseable {
564         private final VirtualDisplaySession mVirtualDisplaySession;
565         private final WindowManagerState.DisplayContent mVirtualDisplay;
566 
SimulatedDisplaySession()567         SimulatedDisplaySession() {
568             mVirtualDisplaySession = new VirtualDisplaySession();
569             mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay();
570         }
571 
getDisplayId()572         int getDisplayId() {
573             return mVirtualDisplay.mId;
574         }
575 
startActivityAndFocus()576         SecondaryActivity startActivityAndFocus() {
577             return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */);
578         }
579 
580         @Override
close()581         public void close() {
582             mVirtualDisplaySession.close();
583         }
584     }
585 
startActivityAndFocus(int displayId, boolean hasFocus)586     private static SecondaryActivity startActivityAndFocus(int displayId, boolean hasFocus) {
587         // An untrusted virtual display won't have focus until the display is touched.
588         final SecondaryActivity activity = WindowManagerTestBase.startActivity(
589                 SecondaryActivity.class, displayId, hasFocus);
590         tapOn(activity);
591         activity.waitAndAssertWindowFocusState(true);
592         return activity;
593     }
594 }
595