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