1 package com.android.server.vr;
2 
3 import static android.view.Display.INVALID_DISPLAY;
4 
5 import android.app.ActivityManagerInternal;
6 import android.app.Vr2dDisplayProperties;
7 import android.content.BroadcastReceiver;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.IntentFilter;
11 import android.graphics.PixelFormat;
12 import android.hardware.display.DisplayManager;
13 import android.hardware.display.VirtualDisplay;
14 import android.hardware.display.VirtualDisplayConfig;
15 import android.media.ImageReader;
16 import android.os.Handler;
17 import android.os.RemoteException;
18 import android.service.vr.IPersistentVrStateCallbacks;
19 import android.service.vr.IVrManager;
20 import android.util.Log;
21 import android.view.Surface;
22 
23 import com.android.server.LocalServices;
24 import com.android.server.wm.ActivityTaskManagerInternal;
25 import com.android.server.wm.WindowManagerInternal;
26 
27 /**
28  * Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and
29  * render 2D app within a VR experience. For example, bringing up the 2D calculator app in VR.
30  */
31 class Vr2dDisplay {
32     private final static String TAG = "Vr2dDisplay";
33     private final static boolean DEBUG = false;
34 
35     private int mVirtualDisplayHeight;
36     private int mVirtualDisplayWidth;
37     private int mVirtualDisplayDpi;
38     private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000;
39     private final static String UNIQUE_DISPLAY_ID = "277f1a09-b88d-4d1e-8716-796f114d080b";
40     private final static String DISPLAY_NAME = "VR 2D Display";
41 
42     private final static String DEBUG_ACTION_SET_MODE =
43             "com.android.server.vr.Vr2dDisplay.SET_MODE";
44     private final static String DEBUG_EXTRA_MODE_ON =
45             "com.android.server.vr.Vr2dDisplay.EXTRA_MODE_ON";
46     private final static String DEBUG_ACTION_SET_SURFACE =
47             "com.android.server.vr.Vr2dDisplay.SET_SURFACE";
48     private final static String DEBUG_EXTRA_SURFACE =
49             "com.android.server.vr.Vr2dDisplay.EXTRA_SURFACE";
50 
51     /**
52      * The default width of the VR virtual display
53      */
54     public static final int DEFAULT_VIRTUAL_DISPLAY_WIDTH = 1400;
55 
56     /**
57      * The default height of the VR virtual display
58      */
59     public static final int DEFAULT_VIRTUAL_DISPLAY_HEIGHT = 1800;
60 
61     /**
62      * The default height of the VR virtual dpi.
63      */
64     public static final int DEFAULT_VIRTUAL_DISPLAY_DPI = 320;
65 
66     /**
67      * The minimum height, width and dpi of VR virtual display.
68      */
69     public static final int MIN_VR_DISPLAY_WIDTH = 1;
70     public static final int MIN_VR_DISPLAY_HEIGHT = 1;
71     public static final int MIN_VR_DISPLAY_DPI = 1;
72 
73     private final ActivityManagerInternal mActivityManagerInternal;
74     private final WindowManagerInternal mWindowManagerInternal;
75     private final DisplayManager mDisplayManager;
76     private final IVrManager mVrManager;
77     private final Object mVdLock = new Object();
78     private final Handler mHandler = new Handler();
79 
80     /**
81      * Callback implementation to receive changes to VrMode.
82      **/
83     private final IPersistentVrStateCallbacks mVrStateCallbacks =
84             new IPersistentVrStateCallbacks.Stub() {
85         @Override
86         public void onPersistentVrStateChanged(boolean enabled) {
87             if (enabled != mIsPersistentVrModeEnabled) {
88                 mIsPersistentVrModeEnabled = enabled;
89                 updateVirtualDisplay();
90             }
91         }
92     };
93 
94     private VirtualDisplay mVirtualDisplay;
95     private Surface mSurface;
96     private ImageReader mImageReader;
97     private Runnable mStopVDRunnable;
98     private boolean mIsVrModeOverrideEnabled;  // debug override to set vr mode.
99     private boolean mIsVirtualDisplayAllowed = true;  // Virtual-display feature toggle
100     private boolean mIsPersistentVrModeEnabled;  // indicates we are in vr persistent mode.
101     private boolean mBootsToVr = false;  // The device boots into VR (standalone VR device)
102 
Vr2dDisplay(DisplayManager displayManager, ActivityManagerInternal activityManagerInternal, WindowManagerInternal windowManagerInternal, IVrManager vrManager)103     public Vr2dDisplay(DisplayManager displayManager,
104            ActivityManagerInternal activityManagerInternal,
105            WindowManagerInternal windowManagerInternal, IVrManager vrManager) {
106         mDisplayManager = displayManager;
107         mActivityManagerInternal = activityManagerInternal;
108         mWindowManagerInternal = windowManagerInternal;
109         mVrManager = vrManager;
110         mVirtualDisplayWidth = DEFAULT_VIRTUAL_DISPLAY_WIDTH;
111         mVirtualDisplayHeight = DEFAULT_VIRTUAL_DISPLAY_HEIGHT;
112         mVirtualDisplayDpi = DEFAULT_VIRTUAL_DISPLAY_DPI;
113     }
114 
115     /**
116      * Initializes the compabilitiy display by listening to VR mode changes.
117      */
init(Context context, boolean bootsToVr)118     public void init(Context context, boolean bootsToVr) {
119         startVrModeListener();
120         startDebugOnlyBroadcastReceiver(context);
121         mBootsToVr = bootsToVr;
122         if (mBootsToVr) {
123           // If we are booting into VR, we need to start the virtual display immediately. This
124           // ensures that the virtual display is up by the time Setup Wizard is started.
125           updateVirtualDisplay();
126         }
127     }
128 
129     /**
130      * Creates and Destroys the virtual display depending on the current state of VrMode.
131      */
updateVirtualDisplay()132     private void updateVirtualDisplay() {
133         if (DEBUG) {
134             Log.i(TAG, "isVrMode: " + mIsPersistentVrModeEnabled + ", override: "
135                     + mIsVrModeOverrideEnabled + ", isAllowed: " + mIsVirtualDisplayAllowed
136                     + ", bootsToVr: " + mBootsToVr);
137         }
138 
139         if (shouldRunVirtualDisplay()) {
140             Log.i(TAG, "Attempting to start virtual display");
141             // TODO: Consider not creating the display until ActivityManager needs one on
142             // which to display a 2D application.
143             startVirtualDisplay();
144         } else {
145             // Stop virtual display to test exit condition
146             stopVirtualDisplay();
147         }
148     }
149 
150     /**
151      * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and
152      * set a custom Surface for the virtual display.  This allows testing of the virtual display
153      * without going into full 3D.
154      *
155      * @param context The context.
156      */
startDebugOnlyBroadcastReceiver(Context context)157     private void startDebugOnlyBroadcastReceiver(Context context) {
158         if (DEBUG) {
159             IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE);
160             intentFilter.addAction(DEBUG_ACTION_SET_SURFACE);
161 
162             context.registerReceiver(new BroadcastReceiver() {
163                 @Override
164                 public void onReceive(Context context, Intent intent) {
165                     final String action = intent.getAction();
166                     if (DEBUG_ACTION_SET_MODE.equals(action)) {
167                         mIsVrModeOverrideEnabled =
168                                 intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false);
169                         updateVirtualDisplay();
170                     } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) {
171                         if (mVirtualDisplay != null) {
172                             if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) {
173                                 setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE, android.view.Surface.class));
174                             }
175                         } else {
176                             Log.w(TAG, "Cannot set the surface because the VD is null.");
177                         }
178                     }
179                 }
180             }, intentFilter, Context.RECEIVER_NOT_EXPORTED);
181         }
182     }
183 
184     /**
185      * Starts listening to VrMode changes.
186      */
startVrModeListener()187     private void startVrModeListener() {
188         if (mVrManager != null) {
189             try {
190                 mVrManager.registerPersistentVrStateListener(mVrStateCallbacks);
191             } catch (RemoteException e) {
192                 Log.e(TAG, "Could not register VR State listener.", e);
193             }
194         }
195     }
196 
197     /**
198      * Sets the resolution and DPI of the Vr2d virtual display used to display
199      * 2D applications in VR mode.
200      *
201      * <p>Requires {@link android.Manifest.permission#ACCESS_VR_MANAGER} permission.</p>
202      *
203      * @param displayProperties Properties of the virtual display for 2D applications
204      * in VR mode.
205      */
setVirtualDisplayProperties(Vr2dDisplayProperties displayProperties)206     public void setVirtualDisplayProperties(Vr2dDisplayProperties displayProperties) {
207         synchronized(mVdLock) {
208             if (DEBUG) {
209                 Log.i(TAG, "VD setVirtualDisplayProperties: " +
210                         displayProperties.toString());
211             }
212 
213             int width = displayProperties.getWidth();
214             int height = displayProperties.getHeight();
215             int dpi = displayProperties.getDpi();
216             boolean resized = false;
217 
218             if (width < MIN_VR_DISPLAY_WIDTH || height < MIN_VR_DISPLAY_HEIGHT ||
219                     dpi < MIN_VR_DISPLAY_DPI) {
220                 Log.i(TAG, "Ignoring Width/Height/Dpi values of " + width + "," + height + ","
221                         + dpi);
222             } else {
223                 Log.i(TAG, "Setting width/height/dpi to " + width + "," + height + "," + dpi);
224                 mVirtualDisplayWidth = width;
225                 mVirtualDisplayHeight = height;
226                 mVirtualDisplayDpi = dpi;
227                 resized = true;
228             }
229 
230             if ((displayProperties.getAddedFlags() &
231                     Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED)
232                     == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) {
233                 mIsVirtualDisplayAllowed = true;
234             } else if ((displayProperties.getRemovedFlags() &
235                     Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED)
236                     == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) {
237                 mIsVirtualDisplayAllowed = false;
238             }
239 
240             if (mVirtualDisplay != null && resized && mIsVirtualDisplayAllowed) {
241                 mVirtualDisplay.resize(mVirtualDisplayWidth, mVirtualDisplayHeight,
242                     mVirtualDisplayDpi);
243                 ImageReader oldImageReader = mImageReader;
244                 mImageReader = null;
245                 startImageReader();
246                 oldImageReader.close();
247             }
248 
249             // Start/Stop the virtual display in case the updates indicated that we should.
250             updateVirtualDisplay();
251         }
252     }
253 
254     /**
255      * Returns the virtual display ID if one currently exists, otherwise returns
256      * {@link INVALID_DISPLAY_ID}.
257      *
258      * @return The virtual display ID.
259      */
getVirtualDisplayId()260     public int getVirtualDisplayId() {
261         synchronized(mVdLock) {
262             if (mVirtualDisplay != null) {
263                 int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
264                 if (DEBUG) {
265                     Log.i(TAG, "VD id: " + virtualDisplayId);
266                 }
267                 return virtualDisplayId;
268             }
269         }
270         return INVALID_DISPLAY;
271     }
272 
273     /**
274      * Starts the virtual display if one does not already exist.
275      */
startVirtualDisplay()276     private void startVirtualDisplay() {
277         if (DEBUG) {
278             Log.d(TAG, "Request to start VD, DM:" + mDisplayManager);
279         }
280 
281         if (mDisplayManager == null) {
282             Log.w(TAG, "Cannot create virtual display because mDisplayManager == null");
283             return;
284         }
285 
286         synchronized (mVdLock) {
287             if (mVirtualDisplay != null) {
288                 Log.i(TAG, "VD already exists, ignoring request");
289                 return;
290             }
291 
292             int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
293             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
294             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
295             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
296             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
297             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
298             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
299 
300             final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
301                     DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi);
302             builder.setUniqueId(UNIQUE_DISPLAY_ID);
303             builder.setFlags(flags);
304             mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
305                     builder.build(), null /* callback */, null /* handler */);
306 
307             if (mVirtualDisplay != null) {
308                 updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
309                 // Now create the ImageReader to supply a Surface to the new virtual display.
310                 startImageReader();
311             } else {
312                 Log.w(TAG, "Virtual display id is null after createVirtualDisplay");
313                 updateDisplayId(INVALID_DISPLAY);
314                 return;
315             }
316         }
317 
318         Log.i(TAG, "VD created: " + mVirtualDisplay);
319     }
320 
updateDisplayId(int displayId)321     private void updateDisplayId(int displayId) {
322         LocalServices.getService(ActivityTaskManagerInternal.class).setVr2dDisplayId(displayId);
323         mWindowManagerInternal.setVr2dDisplayId(displayId);
324     }
325 
326     /**
327      * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
328      * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
329      * of being enabled. This can happen sometimes with our 2D test app.
330      */
stopVirtualDisplay()331     private void stopVirtualDisplay() {
332         if (mStopVDRunnable == null) {
333            mStopVDRunnable = new Runnable() {
334                @Override
335                public void run() {
336                     if (shouldRunVirtualDisplay()) {
337                         Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on.");
338                     } else {
339                         Log.i(TAG, "Stopping Virtual Display");
340                         synchronized (mVdLock) {
341                             updateDisplayId(INVALID_DISPLAY);
342                             setSurfaceLocked(null); // clean up and release the surface first.
343                             if (mVirtualDisplay != null) {
344                                 mVirtualDisplay.release();
345                                 mVirtualDisplay = null;
346                             }
347                             stopImageReader();
348                         }
349                     }
350                }
351            };
352         }
353 
354         mHandler.removeCallbacks(mStopVDRunnable);
355         mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS);
356     }
357 
358     /**
359      * Set the surface to use with the virtual display.
360      *
361      * Code should be locked by {@link #mVdLock} before invoked.
362      *
363      * @param surface The Surface to set.
364      */
setSurfaceLocked(Surface surface)365     private void setSurfaceLocked(Surface surface) {
366         // Change the surface to either a valid surface or a null value.
367         if (mSurface != surface && (surface == null || surface.isValid())) {
368             Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface);
369             if (mVirtualDisplay != null) {
370                 mVirtualDisplay.setSurface(surface);
371             }
372             if (mSurface != null) {
373                 mSurface.release();
374             }
375             mSurface = surface;
376         }
377     }
378 
379     /**
380      * Starts an ImageReader as a do-nothing Surface.  The virtual display will not get fully
381      * initialized within surface flinger unless it has a valid Surface associated with it. We use
382      * the ImageReader as the default valid Surface.
383      */
startImageReader()384     private void startImageReader() {
385         if (mImageReader == null) {
386             mImageReader = ImageReader.newInstance(mVirtualDisplayWidth, mVirtualDisplayHeight,
387                 PixelFormat.RGBA_8888, 2 /* maxImages */);
388             Log.i(TAG, "VD startImageReader: res = " + mVirtualDisplayWidth + "X" +
389                     mVirtualDisplayHeight + ", dpi = " + mVirtualDisplayDpi);
390         }
391         synchronized (mVdLock) {
392             setSurfaceLocked(mImageReader.getSurface());
393         }
394     }
395 
396     /**
397      * Cleans up the ImageReader.
398      */
stopImageReader()399     private void stopImageReader() {
400         if (mImageReader != null) {
401             mImageReader.close();
402             mImageReader = null;
403         }
404     }
405 
shouldRunVirtualDisplay()406     private boolean shouldRunVirtualDisplay() {
407         // Virtual Display should run whenever:
408         // * Virtual Display is allowed/enabled AND
409         // (1) BootsToVr is set indicating the device never leaves VR
410         // (2) VR (persistent) mode is enabled
411         // (3) VR mode is overridden to be enabled.
412         return mIsVirtualDisplayAllowed &&
413                 (mBootsToVr || mIsPersistentVrModeEnabled || mIsVrModeOverrideEnabled);
414     }
415 }
416