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