Home | History | Annotate | Download | only in vr
      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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    396     private void stopImageReader() {
    397         if (mImageReader != null) {
    398             mImageReader.close();
    399             mImageReader = null;
    400         }
    401     }
    402 
    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