• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.display.cts;
18 
19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
21 
22 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertThrows;
29 import static org.junit.Assert.assertTrue;
30 import static org.junit.Assume.assumeFalse;
31 import static org.junit.Assume.assumeTrue;
32 
33 import android.Manifest;
34 import android.app.ActivityOptions;
35 import android.app.Presentation;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.ActivityInfo;
39 import android.content.pm.PackageManager;
40 import android.content.res.Resources;
41 import android.graphics.Color;
42 import android.graphics.Insets;
43 import android.graphics.PixelFormat;
44 import android.graphics.Point;
45 import android.graphics.Rect;
46 import android.graphics.drawable.ColorDrawable;
47 import android.hardware.display.DisplayManager;
48 import android.hardware.display.VirtualDisplay;
49 import android.hardware.display.VirtualDisplayConfig;
50 import android.media.Image;
51 import android.media.ImageReader;
52 import android.os.Bundle;
53 import android.os.Handler;
54 import android.os.HandlerThread;
55 import android.os.Looper;
56 import android.os.SystemClock;
57 import android.platform.test.annotations.AppModeSdkSandbox;
58 import android.platform.test.annotations.AsbSecurityTest;
59 import android.platform.test.annotations.RequiresFlagsEnabled;
60 import android.platform.test.flag.junit.CheckFlagsRule;
61 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
62 import android.provider.Settings;
63 import android.server.wm.IgnoreOrientationRequestSession;
64 import android.server.wm.UiDeviceUtils;
65 import android.server.wm.WindowManagerStateHelper;
66 import android.util.DisplayMetrics;
67 import android.util.Log;
68 import android.view.Display;
69 import android.view.DisplayCutout;
70 import android.view.Surface;
71 import android.view.ViewGroup.LayoutParams;
72 import android.widget.ImageView;
73 
74 import androidx.test.ext.junit.runners.AndroidJUnit4;
75 import androidx.test.platform.app.InstrumentationRegistry;
76 
77 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
78 import com.android.compatibility.common.util.DisplayStateManager;
79 import com.android.compatibility.common.util.FeatureUtil;
80 import com.android.compatibility.common.util.SettingsStateKeeperRule;
81 import com.android.compatibility.common.util.StateKeeperRule;
82 import com.android.compatibility.common.util.SystemUtil;
83 import com.android.compatibility.common.util.UserSettings.Namespace;
84 
85 import org.junit.After;
86 import org.junit.Before;
87 import org.junit.ClassRule;
88 import org.junit.Rule;
89 import org.junit.Test;
90 import org.junit.runner.RunWith;
91 
92 import java.nio.ByteBuffer;
93 import java.util.concurrent.CountDownLatch;
94 import java.util.concurrent.TimeUnit;
95 import java.util.concurrent.locks.Lock;
96 import java.util.concurrent.locks.ReentrantLock;
97 
98 /**
99  * Tests that applications can create virtual displays and present content on them.
100  *
101  * This CTS test is unable to test public virtual displays since special permissions
102  * are required.  See also framework VirtualDisplayTest unit tests.
103  */
104 @RunWith(AndroidJUnit4.class)
105 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
106 public class VirtualDisplayTest {
107     private static final String TAG = "VirtualDisplayTest";
108 
109     private static final String NAME = TAG;
110     private static final int WIDTH = 720;
111     private static final int HEIGHT = 480;
112     private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
113     private static final float REQUESTED_REFRESH_RATE = 30.0f;
114     private static final int TIMEOUT = 40000;
115 
116     // Colors that we use as a signal to determine whether some desired content was
117     // drawn.  The colors themselves doesn't matter but we choose them to have with distinct
118     // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues.
119     // We should only observe RGBA buffers but some graphics drivers might incorrectly
120     // deliver BGRA buffers to virtual displays instead.
121     private static final int BLUEISH = 0xff1122ee;
122     private static final int GREENISH = 0xff33dd44;
123 
124     private Context mContext;
125     private DisplayManager mDisplayManager;
126     private WindowManagerStateHelper mWindowManagerStateHelper;
127     private Handler mHandler;
128     private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/);
129     private ImageReader mImageReader;
130     private Surface mSurface;
131     private ImageListener mImageListener;
132     private HandlerThread mCheckThread;
133     private Handler mCheckHandler;
134 
135     @Rule(order = 0)
136     public AdoptShellPermissionsRule mAdoptShellPermissionsRule =
137             new AdoptShellPermissionsRule(
138                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
139                     Manifest.permission.ADD_TRUSTED_DISPLAY,
140                     Manifest.permission.WRITE_SECURE_SETTINGS);
141 
142     @ClassRule
143     public static final SettingsStateKeeperRule mAreUserDisabledHdrFormatsAllowedSettingsKeeper =
144             new SettingsStateKeeperRule(
145                     InstrumentationRegistry.getInstrumentation().getTargetContext(),
146                     Namespace.GLOBAL, Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED);
147 
148     @ClassRule
149     public static final SettingsStateKeeperRule mUserDisabledHdrFormatsSettingsKeeper =
150             new SettingsStateKeeperRule(
151                     InstrumentationRegistry.getInstrumentation().getTargetContext(),
152                     Namespace.GLOBAL, Settings.Global.USER_DISABLED_HDR_FORMATS);
153 
154     @Rule(order = 1)
155     public StateKeeperRule<DisplayStateManager.DisplayState> mDisplayManagerStateKeeper =
156             new StateKeeperRule<>(new DisplayStateManager(
157                     InstrumentationRegistry.getInstrumentation().getTargetContext()));
158 
159     @Rule
160     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
161 
162     @Before
setUp()163     public void setUp() throws Exception {
164         mContext = InstrumentationRegistry.getInstrumentation().getContext();
165         mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
166         mWindowManagerStateHelper = new WindowManagerStateHelper();
167         mHandler = new Handler(Looper.getMainLooper());
168         mImageListener = new ImageListener();
169         // thread for image checking
170         mCheckThread = new HandlerThread("TestHandler");
171         mCheckThread.start();
172         mCheckHandler = new Handler(mCheckThread.getLooper());
173 
174         mImageReaderLock.lock();
175         try {
176             mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
177             mImageReader.setOnImageAvailableListener(mImageListener, mCheckHandler);
178             mSurface = mImageReader.getSurface();
179         } finally {
180             mImageReaderLock.unlock();
181         }
182     }
183 
184     @After
tearDown()185     public void tearDown() throws Exception {
186         mImageReaderLock.lock();
187         try {
188             mImageReader.close();
189             mImageReader = null;
190             mSurface = null;
191         } finally {
192             mImageReaderLock.unlock();
193         }
194         mCheckThread.quit();
195     }
196 
197     /**
198      * Ensures that an application can create a private virtual display and show
199      * its own windows on it.
200      */
201     @Test
202     @AsbSecurityTest(cveBugId = 141745510)
testPrivateVirtualDisplay()203     public void testPrivateVirtualDisplay() throws Exception {
204         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
205                 WIDTH, HEIGHT, DENSITY, mSurface, 0);
206         assertNotNull("virtual display must not be null", virtualDisplay);
207 
208         Display display = virtualDisplay.getDisplay();
209         try {
210             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
211             assertEquals(mSurface, virtualDisplay.getSurface());
212 
213             // Show a private presentation on the display.
214             assertDisplayCanShowPresentation("private presentation window",
215                     display, BLUEISH, 0);
216         } finally {
217             virtualDisplay.release();
218         }
219         assertDisplayUnregistered(display);
220     }
221 
222     /**
223      * Ensures that an application can create a private presentation virtual display and show
224      * its own windows on it.
225      */
226     @Test
227     @AsbSecurityTest(cveBugId = 141745510)
testPrivatePresentationVirtualDisplay()228     public void testPrivatePresentationVirtualDisplay() throws Exception {
229         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
230                 WIDTH, HEIGHT, DENSITY, mSurface,
231                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
232         assertNotNull("virtual display must not be null", virtualDisplay);
233 
234         Display display = virtualDisplay.getDisplay();
235         try {
236             assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION);
237             assertEquals(mSurface, virtualDisplay.getSurface());
238 
239             // Show a private presentation on the display.
240             assertDisplayCanShowPresentation("private presentation window",
241                     display, BLUEISH, 0);
242         } finally {
243             virtualDisplay.release();
244         }
245         assertDisplayUnregistered(display);
246     }
247 
248     /**
249      * Ensures that an application can create a private virtual display and show
250      * its own windows on it where the surface is attached or detached dynamically.
251      */
252     @Test
253     @AsbSecurityTest(cveBugId = 141745510)
testPrivateVirtualDisplayWithDynamicSurface()254     public void testPrivateVirtualDisplayWithDynamicSurface() throws Exception {
255         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
256                 WIDTH, HEIGHT, DENSITY, null, 0);
257         assertNotNull("virtual display must not be null", virtualDisplay);
258 
259         Display display = virtualDisplay.getDisplay();
260         try {
261             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
262             assertNull(virtualDisplay.getSurface());
263 
264             // Attach the surface.
265             virtualDisplay.setSurface(mSurface);
266             assertEquals(mSurface, virtualDisplay.getSurface());
267 
268             // Show a private presentation on the display.
269             assertDisplayCanShowPresentation("private presentation window",
270                     display, BLUEISH, 0);
271 
272             // Detach the surface.
273             virtualDisplay.setSurface(null);
274             assertNull(virtualDisplay.getSurface());
275         } finally {
276             virtualDisplay.release();
277         }
278         assertDisplayUnregistered(display);
279     }
280 
281     /**
282      * Ensures that {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} will
283      * be clear if an application creates an virtual display without the
284      * flag {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED}.
285      */
286     @Test
testUntrustedSysDecorVirtualDisplay()287     public void testUntrustedSysDecorVirtualDisplay() throws Exception {
288         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
289                 WIDTH, HEIGHT, DENSITY, mSurface,
290                 VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS);
291         assertNotNull("virtual display must not be null", virtualDisplay);
292 
293         Display display = virtualDisplay.getDisplay();
294         try {
295             // Verify that the created virtual display doesn't have flags
296             // FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.
297             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
298             assertEquals(mSurface, virtualDisplay.getSurface());
299 
300             // Show a private presentation on the display.
301             assertDisplayCanShowPresentation("private presentation window",
302                     display, BLUEISH, 0);
303         } finally {
304             virtualDisplay.release();
305         }
306         assertDisplayUnregistered(display);
307     }
308 
309     /**
310      * Ensures that throws {@link SecurityException} when an application creates a trusted virtual
311      * display without holding the permission {@code ADD_TRUSTED_DISPLAY}.
312      */
313     @Test
testTrustedVirtualDisplay()314     public void testTrustedVirtualDisplay() {
315         InstrumentationRegistry.getInstrumentation()
316                 .getUiAutomation()
317                 .dropShellPermissionIdentity();
318         assertThrows(
319                 "SecurityException must be thrown if a trusted virtual display is created without"
320                         + "holding the permission ADD_TRUSTED_DISPLAY.",
321                 SecurityException.class,
322                 () -> mDisplayManager.createVirtualDisplay(
323                         NAME, WIDTH, HEIGHT, DENSITY, mSurface, VIRTUAL_DISPLAY_FLAG_TRUSTED));
324     }
325 
326     /**
327      * Ensures that detaching the display surface turns the display off and attaching a surface will
328      * turn it on only if the power group is already on.
329      */
330     @Test
testSetSurface_togglesDisplayState()331     public void testSetSurface_togglesDisplayState() throws Exception {
332         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
333                 WIDTH, HEIGHT, DENSITY, mSurface,
334                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
335                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
336                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
337         Display display = virtualDisplay.getDisplay();
338         launchTestActivityOnDisplay(display.getDisplayId());
339 
340         try {
341             assertEquals(display.getState(), Display.STATE_ON);
342             {
343                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
344                 virtualDisplay.setSurface(null);
345                 assertTrue(waiter.stateChanged());
346                 assertEquals(display.getState(), Display.STATE_OFF);
347             }
348             {
349                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
350                 virtualDisplay.setSurface(mSurface);
351                 assertTrue(waiter.stateChanged());
352                 assertEquals(display.getState(), Display.STATE_ON);
353             }
354         } finally {
355             virtualDisplay.release();
356         }
357         assertDisplayUnregistered(display);
358     }
359 
360     /**
361      * Ensures that the power group state is propagated to the display state and that attaching the
362      * display surface does not turn on the display if the power group is off.
363      */
364     @Test
testSetSurface_powerGroupOff_doesNotTurnOnDisplay()365     public void testSetSurface_powerGroupOff_doesNotTurnOnDisplay() throws Exception {
366         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
367                 WIDTH, HEIGHT, DENSITY, mSurface,
368                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
369                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
370                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
371         Display display = virtualDisplay.getDisplay();
372         launchTestActivityOnDisplay(display.getDisplayId());
373 
374         try {
375             assertEquals(display.getState(), Display.STATE_ON);
376             {
377                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
378                 UiDeviceUtils.pressSleepButton();
379                 assertTrue(waiter.stateChanged());
380                 assertEquals(display.getState(), Display.STATE_OFF);
381             }
382             {
383                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
384                 virtualDisplay.setSurface(null);
385                 assertFalse(waiter.stateChanged());
386                 assertEquals(display.getState(), Display.STATE_OFF);
387             }
388             {
389                 // Attaching a surface does not turn on the display because the power group is off.
390                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
391                 virtualDisplay.setSurface(mSurface);
392                 assertFalse(waiter.stateChanged());
393                 assertEquals(display.getState(), Display.STATE_OFF);
394             }
395             {
396                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
397                 UiDeviceUtils.pressWakeupButton();
398                 assertTrue(waiter.stateChanged());
399                 assertEquals(display.getState(), Display.STATE_ON);
400             }
401         } finally {
402             UiDeviceUtils.pressWakeupButton();
403             virtualDisplay.release();
404         }
405         assertDisplayUnregistered(display);
406     }
407 
408     /**
409      * Ensures that the power group state is reflected in the display state upon its creation.
410      */
411     @Test
testCreateDisplay_nonNullSurface_powerGroupOff_displayStateIsOff()412     public void testCreateDisplay_nonNullSurface_powerGroupOff_displayStateIsOff() {
413         assumeScreenOffSupported();
414 
415         VirtualDisplay virtualDisplay = null;
416         Display display = null;
417         try {
418             UiDeviceUtils.pressSleepButton();
419             virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
420                     WIDTH, HEIGHT, DENSITY, mSurface,
421                     DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
422                             | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
423                             | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
424             display = virtualDisplay.getDisplay();
425             launchTestActivityOnDisplay(display.getDisplayId());
426             assertEquals(display.getState(), Display.STATE_OFF);
427         } finally {
428             UiDeviceUtils.pressWakeupButton();
429             if (virtualDisplay != null) {
430                 virtualDisplay.release();
431             }
432         }
433         if (display != null) {
434             assertDisplayUnregistered(display);
435         }
436     }
437 
438     /**
439      * Ensures that an application can create a private virtual display with a requested
440      * refresh rate and show its own windows on it.
441      */
442     @Test
testVirtualDisplayWithRequestedRefreshRate()443     public void testVirtualDisplayWithRequestedRefreshRate() throws Exception {
444         VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY)
445                 .setSurface(mSurface)
446                 .setRequestedRefreshRate(REQUESTED_REFRESH_RATE)
447                 .build();
448         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(config);
449         assertNotNull("virtual display must not be null", virtualDisplay);
450         Display display = virtualDisplay.getDisplay();
451         try {
452             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
453             assertEquals(mSurface, virtualDisplay.getSurface());
454 
455             assertEquals(display.getRefreshRate(), REQUESTED_REFRESH_RATE, 0.1f);
456         } finally {
457             virtualDisplay.release();
458         }
459         assertDisplayUnregistered(display);
460     }
461 
462     /**
463      * Ensures that an application can create a virtual display without a cutout.
464      */
465     @Test
testVirtualDisplayWithoutCutout()466     public void testVirtualDisplayWithoutCutout() {
467         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
468                 new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY)
469                         .setSurface(mSurface)
470                         .build());
471         try {
472             assertNull(virtualDisplay.getDisplay().getCutout());
473         } finally {
474             virtualDisplay.release();
475         }
476         assertDisplayUnregistered(virtualDisplay.getDisplay());
477     }
478 
479     /**
480      * Ensures that an application can create a virtual display with a cutout.
481      */
482     @RequiresFlagsEnabled(android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_INSETS)
483     @Test
testVirtualDisplayWithCutout()484     public void testVirtualDisplayWithCutout() {
485         DisplayCutout cutout = new DisplayCutout(
486                 /* safeInsets= */ Insets.of(0, 0, 0, 0),
487                 /* boundLeft= */ new Rect(5, 6, 7, 8),
488                 /* boundTop= */ new Rect(9, 10, 11, 12),
489                 /* boundRight= */ new Rect(13, 14, 15, 16),
490                 /* boundBottom= */ new Rect(17, 18, 19, 20),
491                 /* waterfallInsets= */ Insets.of(21, 22, 23, 24));
492         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
493                 new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY)
494                         .setSurface(mSurface)
495                         .setDisplayCutout(cutout)
496                         .build());
497         try {
498             // The safe insets are always computed by WindowManager based on the waterfall insets
499             // and the bounds. The values passed to the DisplayCutout constructor don't matter as
500             // they will be overridden by WindowManager. So do not validate the safe insets.
501             DisplayCutout actualCutout = virtualDisplay.getDisplay().getCutout();
502             assertEquals(actualCutout.getBoundingRectLeft(), cutout.getBoundingRectLeft());
503             assertEquals(actualCutout.getBoundingRectTop(), cutout.getBoundingRectTop());
504             assertEquals(actualCutout.getBoundingRectRight(), cutout.getBoundingRectRight());
505             assertEquals(actualCutout.getBoundingRectBottom(), cutout.getBoundingRectBottom());
506             assertEquals(actualCutout.getWaterfallInsets(), cutout.getWaterfallInsets());
507         } finally {
508             virtualDisplay.release();
509         }
510         assertDisplayUnregistered(virtualDisplay.getDisplay());
511     }
512 
513     @Test
testVirtualDisplayRotatesWithContent()514     public void testVirtualDisplayRotatesWithContent() throws Exception {
515         assumeTrue(supportsRotation());
516 
517         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
518                 WIDTH, HEIGHT, DENSITY, mSurface,
519                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
520                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
521                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
522                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT);
523         assertNotNull("virtual display must not be null", virtualDisplay);
524 
525         Display display = virtualDisplay.getDisplay();
526         assertEquals(Surface.ROTATION_0, display.getRotation());
527         SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId());
528         try (IgnoreOrientationRequestSession unused =
529                      new IgnoreOrientationRequestSession(/* enable= */ false)) {
530             {
531                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
532                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
533                 assertTrue(waiter.rotationChanged());
534                 assertEquals(getExpectedPortraitRotation(), display.getRotation());
535             }
536             {
537                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
538                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
539                 assertTrue(waiter.rotationChanged());
540                 assertEquals(Surface.ROTATION_0, display.getRotation());
541             }
542         } finally {
543             virtualDisplay.release();
544         }
545         assertDisplayUnregistered(display);
546     }
547 
548     @Test
testVirtualDisplayDoesNotRotateWithContent()549     public void testVirtualDisplayDoesNotRotateWithContent() throws Exception {
550         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
551                 WIDTH, HEIGHT, DENSITY, mSurface,
552                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
553                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
554                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
555         assertNotNull("virtual display must not be null", virtualDisplay);
556 
557         Display display = virtualDisplay.getDisplay();
558         assertEquals(Surface.ROTATION_0, display.getRotation());
559         SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId());
560         try {
561             {
562                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
563                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
564                 assertFalse(waiter.rotationChanged());
565                 assertEquals(Surface.ROTATION_0, display.getRotation());
566             }
567             {
568                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
569                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
570                 assertFalse(waiter.rotationChanged());
571                 assertEquals(Surface.ROTATION_0, display.getRotation());
572             }
573         } finally {
574             // Clean up after the test completes.
575             activity.finish();
576             virtualDisplay.release();
577         }
578         assertDisplayUnregistered(display);
579     }
580 
581     @Test
582     @RequiresFlagsEnabled(
583             android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_ROTATION_API)
testRotateVirtualDisplay_invalidRotationValue_throws()584     public void testRotateVirtualDisplay_invalidRotationValue_throws() {
585         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
586                 WIDTH, HEIGHT, DENSITY, mSurface, /* flags= */ 0);
587         assertNotNull("virtual display must not be null", virtualDisplay);
588 
589         try {
590             assertThrows(IllegalArgumentException.class, () -> virtualDisplay.setRotation(-1));
591             assertThrows(IllegalArgumentException.class, () -> virtualDisplay.setRotation(4));
592         } finally {
593             virtualDisplay.release();
594         }
595         assertDisplayUnregistered(virtualDisplay.getDisplay());
596     }
597 
598     @Test
599     @RequiresFlagsEnabled(
600             android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_ROTATION_API)
testRotateVirtualDisplay()601     public void testRotateVirtualDisplay() throws Exception {
602         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
603                 WIDTH, HEIGHT, DENSITY, mSurface,
604                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
605                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
606                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
607         assertNotNull("virtual display must not be null", virtualDisplay);
608 
609         Display display = virtualDisplay.getDisplay();
610         assertEquals(Surface.ROTATION_0, display.getRotation());
611         // Without an activity we're not going to receive display rotation changes
612         launchTestActivityOnDisplay(display.getDisplayId());
613         try {
614             {
615                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
616                 virtualDisplay.setRotation(Surface.ROTATION_270);
617                 assertTrue(waiter.rotationChanged());
618                 assertEquals(Surface.ROTATION_270, display.getRotation());
619 
620             }
621             {
622                 // Set the current rotation as the new rotation and check that this does NOT
623                 // result in a rotation event.
624                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
625                 virtualDisplay.setRotation(Surface.ROTATION_270);
626                 assertFalse(waiter.rotationChanged());
627                 assertEquals(Surface.ROTATION_270, display.getRotation());
628             }
629             {
630                 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display);
631                 virtualDisplay.setRotation(Surface.ROTATION_0);
632                 assertTrue(waiter.rotationChanged());
633                 assertEquals(Surface.ROTATION_0, display.getRotation());
634             }
635         } finally {
636             virtualDisplay.release();
637         }
638         assertDisplayUnregistered(display);
639     }
640 
641     @Test
testHdrApiMethods()642     public void testHdrApiMethods() {
643         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
644                 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0);
645         try {
646             assertFalse(virtualDisplay.getDisplay().isHdr());
647             assertNull(virtualDisplay.getDisplay().getHdrCapabilities());
648         } finally {
649             virtualDisplay.release();
650         }
651     }
652 
653     @Test
testGetHdrCapabilitiesWithUserDisabledFormats()654     public void testGetHdrCapabilitiesWithUserDisabledFormats() {
655         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
656                 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0);
657         mDisplayManager.setAreUserDisabledHdrTypesAllowed(false);
658         int[] userDisabledHdrTypes = {
659                 Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION,
660                 Display.HdrCapabilities.HDR_TYPE_HLG};
661         mDisplayManager.setUserDisabledHdrTypes(userDisabledHdrTypes);
662 
663         try {
664             assertFalse(virtualDisplay.getDisplay().isHdr());
665             assertNull(virtualDisplay.getDisplay().getHdrCapabilities());
666         } finally {
667             virtualDisplay.release();
668         }
669     }
670 
assertDisplayRegistered(Display display, int flags)671     private void assertDisplayRegistered(Display display, int flags) {
672         assertNotNull("display object must not be null", display);
673         assertTrue("display must be valid", display.isValid());
674         assertTrue("display id must be unique",
675                 display.getDisplayId() != Display.DEFAULT_DISPLAY);
676         assertEquals("display must have correct flags", flags, display.getFlags());
677         assertEquals("display name must match supplied name", NAME, display.getName());
678         Point size = new Point();
679         display.getSize(size);
680         assertEquals("display width must match supplied width", WIDTH, size.x);
681         assertEquals("display height must match supplied height", HEIGHT, size.y);
682         assertEquals("display rotation must be 0",
683                 Surface.ROTATION_0, display.getRotation());
684         assertNotNull("display must be registered",
685                 findDisplay(mDisplayManager.getDisplays(), NAME));
686 
687         if ((flags & Display.FLAG_PRESENTATION) != 0) {
688             assertNotNull("display must be registered as a presentation display",
689                     findDisplay(mDisplayManager.getDisplays(
690                             DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME));
691         } else {
692             assertNull("display must not be registered as a presentation display",
693                     findDisplay(mDisplayManager.getDisplays(
694                             DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME));
695         }
696     }
697 
assertDisplayUnregistered(Display display)698     private void assertDisplayUnregistered(Display display) {
699         assertNull("display must no longer be registered after being removed",
700                 findDisplay(mDisplayManager.getDisplays(), NAME));
701         assertFalse("display must no longer be valid", display.isValid());
702     }
703 
assertDisplayCanShowPresentation(String message, final Display display, final int color, final int windowFlags)704     private void assertDisplayCanShowPresentation(String message, final Display display,
705             final int color, final int windowFlags) {
706         // At this point, we should not have seen any blue.
707         assertTrue(message + ": display should not show content before window is shown",
708                 mImageListener.getColor() != color);
709 
710         final TestPresentation[] presentation = new TestPresentation[1];
711         try {
712             // Show the presentation.
713             runOnUiThread(new Runnable() {
714                 @Override
715                 public void run() {
716                     presentation[0] = new TestPresentation(mContext, display,
717                             color, windowFlags);
718                     presentation[0].show();
719                 }
720             });
721 
722             // Wait for the blue to be seen.
723             assertTrue(message + ": display should show content after window is shown",
724                     mImageListener.waitForColor(color, TIMEOUT));
725         } finally {
726             if (presentation[0] != null) {
727                 runOnUiThread(new Runnable() {
728                     @Override
729                     public void run() {
730                         presentation[0].dismiss();
731                     }
732                 });
733             }
734         }
735     }
736 
assumeScreenOffSupported()737     private void assumeScreenOffSupported() {
738         assumeFalse(
739                 "Skipping test: Automotive main display is always on",
740                 FeatureUtil.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
741         assumeFalse(
742                 "Skipping test: TVs may start screen saver instead of turning screen off",
743                 FeatureUtil.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
744     }
745 
launchTestActivityOnDisplay(int displayId)746     private SimpleActivity launchTestActivityOnDisplay(int displayId) {
747         assumeTrue(supportsActivitiesOnSecondaryDisplays());
748         Intent intent = new Intent(getApplicationContext(), SimpleActivity.class);
749         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
750         ActivityOptions activityOptions = ActivityOptions.makeBasic();
751         activityOptions.setLaunchDisplayId(displayId);
752         return (SimpleActivity) SystemUtil.runWithShellPermissionIdentity(
753                 () -> InstrumentationRegistry.getInstrumentation()
754                         .startActivitySync(intent, activityOptions.toBundle()),
755                 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
756     }
757 
supportsActivitiesOnSecondaryDisplays()758     private boolean supportsActivitiesOnSecondaryDisplays() {
759         return mContext.getPackageManager().hasSystemFeature(
760                 PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
761     }
762 
supportsRotation()763     private boolean supportsRotation() {
764         final boolean supportsLandscape = mContext.getPackageManager().hasSystemFeature(
765                 PackageManager.FEATURE_SCREEN_LANDSCAPE);
766         final boolean supportsPortrait = mContext.getPackageManager().hasSystemFeature(
767                 PackageManager.FEATURE_SCREEN_PORTRAIT);
768         mWindowManagerStateHelper.computeState();
769         final boolean isFixedToUserRotation = mWindowManagerStateHelper.isFixedToUserRotation();
770         return (supportsLandscape && supportsPortrait && !isFixedToUserRotation)
771                 || (!supportsLandscape && !supportsPortrait && !isFixedToUserRotation);
772     }
773 
runOnUiThread(Runnable runnable)774     private void runOnUiThread(Runnable runnable) {
775         Runnable waiter = new Runnable() {
776             @Override
777             public void run() {
778                 synchronized (this) {
779                     notifyAll();
780                 }
781             }
782         };
783         synchronized (waiter) {
784             mHandler.post(runnable);
785             mHandler.post(waiter);
786             try {
787                 waiter.wait(TIMEOUT);
788             } catch (InterruptedException ex) {
789             }
790         }
791     }
792 
findDisplay(Display[] displays, String name)793     private Display findDisplay(Display[] displays, String name) {
794         for (int i = 0; i < displays.length; i++) {
795             if (displays[i].getName().equals(name)) {
796                 return displays[i];
797             }
798         }
799         return null;
800     }
801 
getExpectedPortraitRotation()802     private int getExpectedPortraitRotation() {
803         if (mContext.getResources().getBoolean(Resources.getSystem().getIdentifier(
804                 "config_reverseDefaultRotation", "bool", "android"))) {
805             return Surface.ROTATION_90;
806         } else {
807             return Surface.ROTATION_270;
808         }
809     }
810 
811     private final class DisplayChangeWaiter {
812         private static final int DISPLAY_CHANGE_TIMEOUT_SECS = 5;
813 
814         private final Display mDisplay;
815         private int mCurrentRotation;
816         private int mCurrentState;
817         final CountDownLatch mRotationChangedLatch = new CountDownLatch(1);
818         final CountDownLatch mStateChangedLatch = new CountDownLatch(1);
819 
820         private final DisplayManager.DisplayListener mListener =
821                 new DisplayManager.DisplayListener() {
822                     @Override
823                     public void onDisplayAdded(int displayId) {}
824 
825                     @Override
826                     public void onDisplayRemoved(int displayId) {}
827 
828                     @Override
829                     public void onDisplayChanged(int displayId) {
830                         if (mDisplay.getDisplayId() != displayId) {
831                             return;
832                         }
833                         if (mCurrentRotation != mDisplay.getRotation()) {
834                             mCurrentRotation = mDisplay.getRotation();
835                             mRotationChangedLatch.countDown();
836                         }
837                         if (mCurrentState != mDisplay.getState()) {
838                             mCurrentState = mDisplay.getState();
839                             mStateChangedLatch.countDown();
840                         }
841                     }
842                 };
843 
DisplayChangeWaiter(Display display)844         DisplayChangeWaiter(Display display) {
845             mDisplay = display;
846             mCurrentRotation = mDisplay.getRotation();
847             mCurrentState = mDisplay.getState();
848             Handler handler = new Handler(Looper.getMainLooper());
849             mDisplayManager.registerDisplayListener(mListener, handler);
850         }
851 
rotationChanged()852         boolean rotationChanged() throws Exception {
853             try {
854                 return mRotationChangedLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
855             } finally {
856                 mDisplayManager.unregisterDisplayListener(mListener);
857             }
858         }
859 
stateChanged()860         boolean stateChanged() throws Exception {
861             try {
862                 return mStateChangedLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
863             } finally {
864                 mDisplayManager.unregisterDisplayListener(mListener);
865             }
866         }
867     }
868 
869     private final class TestPresentation extends Presentation {
870         private final int mColor;
871         private final int mWindowFlags;
872 
TestPresentation(Context context, Display display, int color, int windowFlags)873         public TestPresentation(Context context, Display display,
874                 int color, int windowFlags) {
875             super(context, display);
876             mColor = color;
877             mWindowFlags = windowFlags;
878         }
879 
880         @Override
onCreate(Bundle savedInstanceState)881         protected void onCreate(Bundle savedInstanceState) {
882             super.onCreate(savedInstanceState);
883 
884             setTitle(TAG);
885             getWindow().addFlags(mWindowFlags);
886 
887             // Create a solid color image to use as the content of the presentation.
888             ImageView view = new ImageView(getContext());
889             view.setImageDrawable(new ColorDrawable(mColor));
890             view.setLayoutParams(new LayoutParams(
891                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
892             setContentView(view);
893         }
894     }
895 
896     /**
897      * Watches for an image with a large amount of some particular solid color to be shown.
898      */
899     private final class ImageListener
900             implements ImageReader.OnImageAvailableListener {
901         private int mColor = -1;
902 
getColor()903         public int getColor() {
904             synchronized (this) {
905                 return mColor;
906             }
907         }
908 
waitForColor(int color, long timeoutMillis)909         public boolean waitForColor(int color, long timeoutMillis) {
910             long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis;
911             synchronized (this) {
912                 while (mColor != color) {
913                     long now = SystemClock.uptimeMillis();
914                     if (now >= timeoutTime) {
915                         return false;
916                     }
917                     try {
918                         wait(timeoutTime - now);
919                     } catch (InterruptedException ex) {
920                     }
921                 }
922                 return true;
923             }
924         }
925 
926         @Override
onImageAvailable(ImageReader reader)927         public void onImageAvailable(ImageReader reader) {
928             mImageReaderLock.lock();
929             try {
930                 if (reader != mImageReader) {
931                     return;
932                 }
933 
934                 Log.d(TAG, "New image available from virtual display.");
935                 // Get the latest buffer
936                 Image image = reader.acquireLatestImage();
937                 if (image != null) {
938                     try {
939                         // Scan for colors.
940                         int color = scanImage(image);
941                         synchronized (this) {
942                             if (mColor != color) {
943                                 mColor = color;
944                                 notifyAll();
945                             }
946                         }
947                     } finally {
948                         image.close();
949                     }
950                 }
951             } finally {
952                 mImageReaderLock.unlock();
953             }
954         }
955 
scanImage(Image image)956         private int scanImage(Image image) {
957             final Image.Plane plane = image.getPlanes()[0];
958             final ByteBuffer buffer = plane.getBuffer();
959             final int width = image.getWidth();
960             final int height = image.getHeight();
961             final int pixelStride = plane.getPixelStride();
962             final int rowStride = plane.getRowStride();
963             final int rowPadding = rowStride - pixelStride * width;
964 
965             Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height
966                     + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride);
967 
968             int offset = 0;
969             int blackPixels = 0;
970             int bluePixels = 0;
971             int greenPixels = 0;
972             int otherPixels = 0;
973             for (int y = 0; y < height; y++) {
974                 for (int x = 0; x < width; x++) {
975                     int pixel = 0;
976                     pixel |= (buffer.get(offset) & 0xff) << 16;     // R
977                     pixel |= (buffer.get(offset + 1) & 0xff) << 8;  // G
978                     pixel |= (buffer.get(offset + 2) & 0xff);       // B
979                     pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
980                     if (pixel == Color.BLACK || pixel == 0) {
981                         blackPixels += 1;
982                     } else if (pixel == BLUEISH) {
983                         bluePixels += 1;
984                     } else if (pixel == GREENISH) {
985                         greenPixels += 1;
986                     } else {
987                         otherPixels += 1;
988                         if (otherPixels < 10) {
989                             Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel));
990                         }
991                     }
992                     offset += pixelStride;
993                 }
994                 offset += rowPadding;
995             }
996 
997             // Return a color if it represents more than one quarter of the pixels.
998             // We use this threshold in case the display is being letterboxed when
999             // mirroring so there might be large black bars on the sides, which is normal.
1000             Log.d(TAG, "- Pixels: " + blackPixels + " black, "
1001                     + bluePixels + " blue, "
1002                     + greenPixels + " green, "
1003                     + otherPixels + " other");
1004             final int threshold = width * height / 4;
1005             if (bluePixels > threshold) {
1006                 Log.d(TAG, "- Reporting blue.");
1007                 return BLUEISH;
1008             }
1009             if (greenPixels > threshold) {
1010                 Log.d(TAG, "- Reporting green.");
1011                 return GREENISH;
1012             }
1013             if (blackPixels > threshold) {
1014                 Log.d(TAG, "- Reporting black.");
1015                 return Color.BLACK;
1016             }
1017             Log.d(TAG, "- Reporting other.");
1018             return -1;
1019         }
1020     }
1021 }
1022 
1023