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.assertTrue;
29 import static org.junit.Assert.fail;
30 import static org.junit.Assume.assumeTrue;
31 
32 import android.Manifest;
33 import android.app.ActivityOptions;
34 import android.app.Presentation;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.pm.ActivityInfo;
38 import android.content.pm.PackageManager;
39 import android.content.res.Resources;
40 import android.graphics.Color;
41 import android.graphics.PixelFormat;
42 import android.graphics.Point;
43 import android.graphics.drawable.ColorDrawable;
44 import android.hardware.display.DisplayManager;
45 import android.hardware.display.VirtualDisplay;
46 import android.hardware.display.VirtualDisplayConfig;
47 import android.media.Image;
48 import android.media.ImageReader;
49 import android.os.Bundle;
50 import android.os.Handler;
51 import android.os.HandlerThread;
52 import android.os.Looper;
53 import android.os.SystemClock;
54 import android.platform.test.annotations.AppModeSdkSandbox;
55 import android.platform.test.annotations.AsbSecurityTest;
56 import android.provider.Settings;
57 import android.server.wm.IgnoreOrientationRequestSession;
58 import android.util.DisplayMetrics;
59 import android.util.Log;
60 import android.view.Display;
61 import android.view.Surface;
62 import android.view.ViewGroup.LayoutParams;
63 import android.widget.ImageView;
64 
65 
66 import androidx.test.ext.junit.runners.AndroidJUnit4;
67 import androidx.test.platform.app.InstrumentationRegistry;
68 
69 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
70 import com.android.compatibility.common.util.DisplayStateManager;
71 import com.android.compatibility.common.util.SettingsStateKeeperRule;
72 import com.android.compatibility.common.util.StateKeeperRule;
73 import com.android.compatibility.common.util.SystemUtil;
74 import com.android.compatibility.common.util.UserSettings.Namespace;
75 
76 import org.junit.After;
77 import org.junit.Before;
78 import org.junit.ClassRule;
79 import org.junit.Rule;
80 import org.junit.Test;
81 import org.junit.runner.RunWith;
82 
83 import java.nio.ByteBuffer;
84 import java.util.concurrent.CountDownLatch;
85 import java.util.concurrent.TimeUnit;
86 import java.util.concurrent.locks.Lock;
87 import java.util.concurrent.locks.ReentrantLock;
88 
89 /**
90  * Tests that applications can create virtual displays and present content on them.
91  *
92  * This CTS test is unable to test public virtual displays since special permissions
93  * are required.  See also framework VirtualDisplayTest unit tests.
94  */
95 @RunWith(AndroidJUnit4.class)
96 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
97 public class VirtualDisplayTest {
98     private static final String TAG = "VirtualDisplayTest";
99 
100     private static final String NAME = TAG;
101     private static final int WIDTH = 720;
102     private static final int HEIGHT = 480;
103     private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
104     private static final float REQUESTED_REFRESH_RATE = 30.0f;
105     private static final int TIMEOUT = 40000;
106 
107     // Colors that we use as a signal to determine whether some desired content was
108     // drawn.  The colors themselves doesn't matter but we choose them to have with distinct
109     // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues.
110     // We should only observe RGBA buffers but some graphics drivers might incorrectly
111     // deliver BGRA buffers to virtual displays instead.
112     private static final int BLUEISH = 0xff1122ee;
113     private static final int GREENISH = 0xff33dd44;
114 
115     private Context mContext;
116     private DisplayManager mDisplayManager;
117     private Handler mHandler;
118     private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/);
119     private ImageReader mImageReader;
120     private Surface mSurface;
121     private ImageListener mImageListener;
122     private HandlerThread mCheckThread;
123     private Handler mCheckHandler;
124 
125     @Rule(order = 0)
126     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
127             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
128             Manifest.permission.ADD_TRUSTED_DISPLAY,
129             Manifest.permission.WRITE_SECURE_SETTINGS);
130 
131     @ClassRule
132     public static final SettingsStateKeeperRule mAreUserDisabledHdrFormatsAllowedSettingsKeeper =
133             new SettingsStateKeeperRule(
134                     InstrumentationRegistry.getInstrumentation().getTargetContext(),
135                     Namespace.GLOBAL, Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED);
136 
137     @ClassRule
138     public static final SettingsStateKeeperRule mUserDisabledHdrFormatsSettingsKeeper =
139             new SettingsStateKeeperRule(
140                     InstrumentationRegistry.getInstrumentation().getTargetContext(),
141                     Namespace.GLOBAL, Settings.Global.USER_DISABLED_HDR_FORMATS);
142 
143     @Rule(order = 1)
144     public StateKeeperRule<DisplayStateManager.DisplayState> mDisplayManagerStateKeeper =
145             new StateKeeperRule<>(new DisplayStateManager(
146                     InstrumentationRegistry.getInstrumentation().getTargetContext()));
147 
148     @Before
setUp()149     public void setUp() throws Exception {
150         mContext = InstrumentationRegistry.getInstrumentation().getContext();
151         mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
152         mHandler = new Handler(Looper.getMainLooper());
153         mImageListener = new ImageListener();
154         // thread for image checking
155         mCheckThread = new HandlerThread("TestHandler");
156         mCheckThread.start();
157         mCheckHandler = new Handler(mCheckThread.getLooper());
158 
159         mImageReaderLock.lock();
160         try {
161             mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
162             mImageReader.setOnImageAvailableListener(mImageListener, mCheckHandler);
163             mSurface = mImageReader.getSurface();
164         } finally {
165             mImageReaderLock.unlock();
166         }
167     }
168 
169     @After
tearDown()170     public void tearDown() throws Exception {
171         mImageReaderLock.lock();
172         try {
173             mImageReader.close();
174             mImageReader = null;
175             mSurface = null;
176         } finally {
177             mImageReaderLock.unlock();
178         }
179         mCheckThread.quit();
180     }
181 
182     /**
183      * Ensures that an application can create a private virtual display and show
184      * its own windows on it.
185      */
186     @Test
187     @AsbSecurityTest(cveBugId = 141745510)
testPrivateVirtualDisplay()188     public void testPrivateVirtualDisplay() throws Exception {
189         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
190                 WIDTH, HEIGHT, DENSITY, mSurface, 0);
191         assertNotNull("virtual display must not be null", virtualDisplay);
192 
193         Display display = virtualDisplay.getDisplay();
194         try {
195             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
196             assertEquals(mSurface, virtualDisplay.getSurface());
197 
198             // Show a private presentation on the display.
199             assertDisplayCanShowPresentation("private presentation window",
200                     display, BLUEISH, 0);
201         } finally {
202             virtualDisplay.release();
203         }
204         assertDisplayUnregistered(display);
205     }
206 
207     /**
208      * Ensures that an application can create a private presentation virtual display and show
209      * its own windows on it.
210      */
211     @Test
212     @AsbSecurityTest(cveBugId = 141745510)
testPrivatePresentationVirtualDisplay()213     public void testPrivatePresentationVirtualDisplay() throws Exception {
214         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
215                 WIDTH, HEIGHT, DENSITY, mSurface,
216                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
217         assertNotNull("virtual display must not be null", virtualDisplay);
218 
219         Display display = virtualDisplay.getDisplay();
220         try {
221             assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION);
222             assertEquals(mSurface, virtualDisplay.getSurface());
223 
224             // Show a private presentation on the display.
225             assertDisplayCanShowPresentation("private presentation window",
226                     display, BLUEISH, 0);
227         } finally {
228             virtualDisplay.release();
229         }
230         assertDisplayUnregistered(display);
231     }
232 
233     /**
234      * Ensures that an application can create a private virtual display and show
235      * its own windows on it where the surface is attached or detached dynamically.
236      */
237     @Test
238     @AsbSecurityTest(cveBugId = 141745510)
testPrivateVirtualDisplayWithDynamicSurface()239     public void testPrivateVirtualDisplayWithDynamicSurface() throws Exception {
240         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
241                 WIDTH, HEIGHT, DENSITY, null, 0);
242         assertNotNull("virtual display must not be null", virtualDisplay);
243 
244         Display display = virtualDisplay.getDisplay();
245         try {
246             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
247             assertNull(virtualDisplay.getSurface());
248 
249             // Attach the surface.
250             virtualDisplay.setSurface(mSurface);
251             assertEquals(mSurface, virtualDisplay.getSurface());
252 
253             // Show a private presentation on the display.
254             assertDisplayCanShowPresentation("private presentation window",
255                     display, BLUEISH, 0);
256 
257             // Detach the surface.
258             virtualDisplay.setSurface(null);
259             assertNull(virtualDisplay.getSurface());
260         } finally {
261             virtualDisplay.release();
262         }
263         assertDisplayUnregistered(display);
264     }
265 
266     /**
267      * Ensures that {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} will
268      * be clear if an application creates an virtual display without the
269      * flag {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED}.
270      */
271     @Test
testUntrustedSysDecorVirtualDisplay()272     public void testUntrustedSysDecorVirtualDisplay() throws Exception {
273         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
274                 WIDTH, HEIGHT, DENSITY, mSurface,
275                 VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS);
276         assertNotNull("virtual display must not be null", virtualDisplay);
277 
278         Display display = virtualDisplay.getDisplay();
279         try {
280             // Verify that the created virtual display doesn't have flags
281             // FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.
282             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
283             assertEquals(mSurface, virtualDisplay.getSurface());
284 
285             // Show a private presentation on the display.
286             assertDisplayCanShowPresentation("private presentation window",
287                     display, BLUEISH, 0);
288         } finally {
289             virtualDisplay.release();
290         }
291         assertDisplayUnregistered(display);
292     }
293 
294     /**
295      * Ensures that throws {@link SecurityException} when an application creates a trusted virtual
296      * display without holding the permission {@code ADD_TRUSTED_DISPLAY}.
297      */
298     @Test
testTrustedVirtualDisplay()299     public void testTrustedVirtualDisplay() throws Exception {
300         InstrumentationRegistry.getInstrumentation().getUiAutomation()
301                 .dropShellPermissionIdentity();
302 
303         try {
304             VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
305                     WIDTH, HEIGHT, DENSITY, mSurface, VIRTUAL_DISPLAY_FLAG_TRUSTED);
306         } catch (SecurityException e) {
307             // Expected.
308             return;
309         }
310         fail("SecurityException must be thrown if a trusted virtual display is created without"
311                 + "holding the permission ADD_TRUSTED_DISPLAY.");
312     }
313 
314     /**
315      * Ensures that an application can create a private virtual display with a requested
316      * refresh rate and show its own windows on it.
317      */
318     @Test
testVirtualDisplayWithRequestedRefreshRate()319     public void testVirtualDisplayWithRequestedRefreshRate() throws Exception {
320         VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY)
321                 .setSurface(mSurface)
322                 .setRequestedRefreshRate(REQUESTED_REFRESH_RATE)
323                 .build();
324         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(config);
325         assertNotNull("virtual display must not be null", virtualDisplay);
326         Display display = virtualDisplay.getDisplay();
327         try {
328             assertDisplayRegistered(display, Display.FLAG_PRIVATE);
329             assertEquals(mSurface, virtualDisplay.getSurface());
330 
331             assertEquals(display.getRefreshRate(), REQUESTED_REFRESH_RATE, 0.1f);
332         } finally {
333             virtualDisplay.release();
334         }
335         assertDisplayUnregistered(display);
336     }
337 
338     @Test
testVirtualDisplayRotatesWithContent()339     public void testVirtualDisplayRotatesWithContent() throws Exception {
340         assumeTrue(supportsActivitiesOnSecondaryDisplays());
341         assumeTrue(supportsRotation());
342 
343         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
344                 WIDTH, HEIGHT, DENSITY, mSurface,
345                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
346                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
347                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
348                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT);
349         assertNotNull("virtual display must not be null", virtualDisplay);
350 
351         Display display = virtualDisplay.getDisplay();
352         assertEquals(Surface.ROTATION_0, display.getRotation());
353         SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId());
354         try (IgnoreOrientationRequestSession unused =
355                      new IgnoreOrientationRequestSession(/* enable= */ false)) {
356             {
357                 RotationChangeWaiter waiter = new RotationChangeWaiter(display);
358                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
359                 assertTrue(waiter.rotationChanged());
360                 assertEquals(getExpectedPortraitRotation(), display.getRotation());
361             }
362             {
363                 RotationChangeWaiter waiter = new RotationChangeWaiter(display);
364                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
365                 assertTrue(waiter.rotationChanged());
366                 assertEquals(Surface.ROTATION_0, display.getRotation());
367             }
368         } finally {
369             virtualDisplay.release();
370         }
371         assertDisplayUnregistered(display);
372     }
373 
374     @Test
testVirtualDisplayDoesNotRotateWithContent()375     public void testVirtualDisplayDoesNotRotateWithContent() throws Exception {
376         assumeTrue(supportsActivitiesOnSecondaryDisplays());
377 
378         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
379                 WIDTH, HEIGHT, DENSITY, mSurface,
380                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
381                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
382                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
383         assertNotNull("virtual display must not be null", virtualDisplay);
384 
385         Display display = virtualDisplay.getDisplay();
386         assertEquals(Surface.ROTATION_0, display.getRotation());
387         SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId());
388         try {
389             {
390                 RotationChangeWaiter waiter = new RotationChangeWaiter(display);
391                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
392                 assertFalse(waiter.rotationChanged());
393                 assertEquals(Surface.ROTATION_0, display.getRotation());
394             }
395             {
396                 RotationChangeWaiter waiter = new RotationChangeWaiter(display);
397                 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
398                 assertFalse(waiter.rotationChanged());
399                 assertEquals(Surface.ROTATION_0, display.getRotation());
400             }
401         } finally {
402             virtualDisplay.release();
403         }
404         assertDisplayUnregistered(display);
405     }
406 
407     @Test
testHdrApiMethods()408     public void testHdrApiMethods() {
409         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
410                 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0);
411         try {
412             assertFalse(virtualDisplay.getDisplay().isHdr());
413             assertNull(virtualDisplay.getDisplay().getHdrCapabilities());
414         } finally {
415             virtualDisplay.release();
416         }
417     }
418 
419     @Test
testGetHdrCapabilitiesWithUserDisabledFormats()420     public void testGetHdrCapabilitiesWithUserDisabledFormats() {
421         VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
422                 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0);
423         mDisplayManager.setAreUserDisabledHdrTypesAllowed(false);
424         int[] userDisabledHdrTypes = {
425                 Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION,
426                 Display.HdrCapabilities.HDR_TYPE_HLG};
427         mDisplayManager.setUserDisabledHdrTypes(userDisabledHdrTypes);
428 
429         try {
430             assertFalse(virtualDisplay.getDisplay().isHdr());
431             assertNull(virtualDisplay.getDisplay().getHdrCapabilities());
432         } finally {
433             virtualDisplay.release();
434         }
435     }
436 
assertDisplayRegistered(Display display, int flags)437     private void assertDisplayRegistered(Display display, int flags) {
438         assertNotNull("display object must not be null", display);
439         assertTrue("display must be valid", display.isValid());
440         assertTrue("display id must be unique",
441                 display.getDisplayId() != Display.DEFAULT_DISPLAY);
442         assertEquals("display must have correct flags", flags, display.getFlags());
443         assertEquals("display name must match supplied name", NAME, display.getName());
444         Point size = new Point();
445         display.getSize(size);
446         assertEquals("display width must match supplied width", WIDTH, size.x);
447         assertEquals("display height must match supplied height", HEIGHT, size.y);
448         assertEquals("display rotation must be 0",
449                 Surface.ROTATION_0, display.getRotation());
450         assertNotNull("display must be registered",
451                 findDisplay(mDisplayManager.getDisplays(), NAME));
452 
453         if ((flags & Display.FLAG_PRESENTATION) != 0) {
454             assertNotNull("display must be registered as a presentation display",
455                     findDisplay(mDisplayManager.getDisplays(
456                             DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME));
457         } else {
458             assertNull("display must not be registered as a presentation display",
459                     findDisplay(mDisplayManager.getDisplays(
460                             DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME));
461         }
462     }
463 
assertDisplayUnregistered(Display display)464     private void assertDisplayUnregistered(Display display) {
465         assertNull("display must no longer be registered after being removed",
466                 findDisplay(mDisplayManager.getDisplays(), NAME));
467         assertFalse("display must no longer be valid", display.isValid());
468     }
469 
assertDisplayCanShowPresentation(String message, final Display display, final int color, final int windowFlags)470     private void assertDisplayCanShowPresentation(String message, final Display display,
471             final int color, final int windowFlags) {
472         // At this point, we should not have seen any blue.
473         assertTrue(message + ": display should not show content before window is shown",
474                 mImageListener.getColor() != color);
475 
476         final TestPresentation[] presentation = new TestPresentation[1];
477         try {
478             // Show the presentation.
479             runOnUiThread(new Runnable() {
480                 @Override
481                 public void run() {
482                     presentation[0] = new TestPresentation(mContext, display,
483                             color, windowFlags);
484                     presentation[0].show();
485                 }
486             });
487 
488             // Wait for the blue to be seen.
489             assertTrue(message + ": display should show content after window is shown",
490                     mImageListener.waitForColor(color, TIMEOUT));
491         } finally {
492             if (presentation[0] != null) {
493                 runOnUiThread(new Runnable() {
494                     @Override
495                     public void run() {
496                         presentation[0].dismiss();
497                     }
498                 });
499             }
500         }
501     }
502 
launchTestActivityOnDisplay(int displayId)503     private SimpleActivity launchTestActivityOnDisplay(int displayId) {
504         Intent intent = new Intent(getApplicationContext(), SimpleActivity.class);
505         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
506         ActivityOptions activityOptions = ActivityOptions.makeBasic();
507         activityOptions.setLaunchDisplayId(displayId);
508         return (SimpleActivity) SystemUtil.runWithShellPermissionIdentity(
509                 () -> InstrumentationRegistry.getInstrumentation()
510                         .startActivitySync(intent, activityOptions.toBundle()),
511                 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
512     }
513 
supportsActivitiesOnSecondaryDisplays()514     private boolean supportsActivitiesOnSecondaryDisplays() {
515         return mContext.getPackageManager().hasSystemFeature(
516                 PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
517     }
518 
supportsRotation()519     private boolean supportsRotation() {
520         final boolean supportsLandscape = mContext.getPackageManager().hasSystemFeature(
521                 PackageManager.FEATURE_SCREEN_LANDSCAPE);
522         final boolean supportsPortrait = mContext.getPackageManager().hasSystemFeature(
523                 PackageManager.FEATURE_SCREEN_PORTRAIT);
524         return (supportsLandscape && supportsPortrait)
525                 || (!supportsLandscape && !supportsPortrait);
526     }
527 
runOnUiThread(Runnable runnable)528     private void runOnUiThread(Runnable runnable) {
529         Runnable waiter = new Runnable() {
530             @Override
531             public void run() {
532                 synchronized (this) {
533                     notifyAll();
534                 }
535             }
536         };
537         synchronized (waiter) {
538             mHandler.post(runnable);
539             mHandler.post(waiter);
540             try {
541                 waiter.wait(TIMEOUT);
542             } catch (InterruptedException ex) {
543             }
544         }
545     }
546 
findDisplay(Display[] displays, String name)547     private Display findDisplay(Display[] displays, String name) {
548         for (int i = 0; i < displays.length; i++) {
549             if (displays[i].getName().equals(name)) {
550                 return displays[i];
551             }
552         }
553         return null;
554     }
555 
getExpectedPortraitRotation()556     private int getExpectedPortraitRotation() {
557         if (mContext.getResources().getBoolean(Resources.getSystem().getIdentifier(
558                 "config_reverseDefaultRotation", "bool", "android"))) {
559             return Surface.ROTATION_90;
560         } else {
561             return Surface.ROTATION_270;
562         }
563     }
564 
565     private final class RotationChangeWaiter {
566         private static final int DISPLAY_CHANGE_TIMEOUT_SECS = 3;
567 
568         private final Display mDisplay;
569         private int mCurrentRotation;
570         final CountDownLatch mRotationChangedLatch = new CountDownLatch(1);
571 
572         private final DisplayManager.DisplayListener mListener =
573                 new DisplayManager.DisplayListener() {
574                     @Override
575                     public void onDisplayAdded(int displayId) {}
576 
577                     @Override
578                     public void onDisplayRemoved(int displayId) {}
579 
580                     @Override
581                     public void onDisplayChanged(int displayId) {
582                         if (mDisplay.getDisplayId() == displayId
583                                 && mCurrentRotation != mDisplay.getRotation()) {
584                             mCurrentRotation = mDisplay.getRotation();
585                             mRotationChangedLatch.countDown();
586                         }
587                     }
588                 };
589 
RotationChangeWaiter(Display display)590         RotationChangeWaiter(Display display) {
591             mDisplay = display;
592             mCurrentRotation = mDisplay.getRotation();
593             Handler handler = new Handler(Looper.getMainLooper());
594             mDisplayManager.registerDisplayListener(mListener, handler);
595         }
596 
rotationChanged()597         boolean rotationChanged() throws Exception {
598             try {
599                 return mRotationChangedLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
600             } finally {
601                 mDisplayManager.unregisterDisplayListener(mListener);
602             }
603         }
604     }
605 
606     private final class TestPresentation extends Presentation {
607         private final int mColor;
608         private final int mWindowFlags;
609 
TestPresentation(Context context, Display display, int color, int windowFlags)610         public TestPresentation(Context context, Display display,
611                 int color, int windowFlags) {
612             super(context, display);
613             mColor = color;
614             mWindowFlags = windowFlags;
615         }
616 
617         @Override
onCreate(Bundle savedInstanceState)618         protected void onCreate(Bundle savedInstanceState) {
619             super.onCreate(savedInstanceState);
620 
621             setTitle(TAG);
622             getWindow().addFlags(mWindowFlags);
623 
624             // Create a solid color image to use as the content of the presentation.
625             ImageView view = new ImageView(getContext());
626             view.setImageDrawable(new ColorDrawable(mColor));
627             view.setLayoutParams(new LayoutParams(
628                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
629             setContentView(view);
630         }
631     }
632 
633     /**
634      * Watches for an image with a large amount of some particular solid color to be shown.
635      */
636     private final class ImageListener
637             implements ImageReader.OnImageAvailableListener {
638         private int mColor = -1;
639 
getColor()640         public int getColor() {
641             synchronized (this) {
642                 return mColor;
643             }
644         }
645 
waitForColor(int color, long timeoutMillis)646         public boolean waitForColor(int color, long timeoutMillis) {
647             long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis;
648             synchronized (this) {
649                 while (mColor != color) {
650                     long now = SystemClock.uptimeMillis();
651                     if (now >= timeoutTime) {
652                         return false;
653                     }
654                     try {
655                         wait(timeoutTime - now);
656                     } catch (InterruptedException ex) {
657                     }
658                 }
659                 return true;
660             }
661         }
662 
663         @Override
onImageAvailable(ImageReader reader)664         public void onImageAvailable(ImageReader reader) {
665             mImageReaderLock.lock();
666             try {
667                 if (reader != mImageReader) {
668                     return;
669                 }
670 
671                 Log.d(TAG, "New image available from virtual display.");
672                 // Get the latest buffer
673                 Image image = reader.acquireLatestImage();
674                 if (image != null) {
675                     try {
676                         // Scan for colors.
677                         int color = scanImage(image);
678                         synchronized (this) {
679                             if (mColor != color) {
680                                 mColor = color;
681                                 notifyAll();
682                             }
683                         }
684                     } finally {
685                         image.close();
686                     }
687                 }
688             } finally {
689                 mImageReaderLock.unlock();
690             }
691         }
692 
scanImage(Image image)693         private int scanImage(Image image) {
694             final Image.Plane plane = image.getPlanes()[0];
695             final ByteBuffer buffer = plane.getBuffer();
696             final int width = image.getWidth();
697             final int height = image.getHeight();
698             final int pixelStride = plane.getPixelStride();
699             final int rowStride = plane.getRowStride();
700             final int rowPadding = rowStride - pixelStride * width;
701 
702             Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height
703                     + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride);
704 
705             int offset = 0;
706             int blackPixels = 0;
707             int bluePixels = 0;
708             int greenPixels = 0;
709             int otherPixels = 0;
710             for (int y = 0; y < height; y++) {
711                 for (int x = 0; x < width; x++) {
712                     int pixel = 0;
713                     pixel |= (buffer.get(offset) & 0xff) << 16;     // R
714                     pixel |= (buffer.get(offset + 1) & 0xff) << 8;  // G
715                     pixel |= (buffer.get(offset + 2) & 0xff);       // B
716                     pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
717                     if (pixel == Color.BLACK || pixel == 0) {
718                         blackPixels += 1;
719                     } else if (pixel == BLUEISH) {
720                         bluePixels += 1;
721                     } else if (pixel == GREENISH) {
722                         greenPixels += 1;
723                     } else {
724                         otherPixels += 1;
725                         if (otherPixels < 10) {
726                             Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel));
727                         }
728                     }
729                     offset += pixelStride;
730                 }
731                 offset += rowPadding;
732             }
733 
734             // Return a color if it represents more than one quarter of the pixels.
735             // We use this threshold in case the display is being letterboxed when
736             // mirroring so there might be large black bars on the sides, which is normal.
737             Log.d(TAG, "- Pixels: " + blackPixels + " black, "
738                     + bluePixels + " blue, "
739                     + greenPixels + " green, "
740                     + otherPixels + " other");
741             final int threshold = width * height / 4;
742             if (bluePixels > threshold) {
743                 Log.d(TAG, "- Reporting blue.");
744                 return BLUEISH;
745             }
746             if (greenPixels > threshold) {
747                 Log.d(TAG, "- Reporting green.");
748                 return GREENISH;
749             }
750             if (blackPixels > threshold) {
751                 Log.d(TAG, "- Reporting black.");
752                 return Color.BLACK;
753             }
754             Log.d(TAG, "- Reporting other.");
755             return -1;
756         }
757     }
758 }
759 
760