OSDN Git Service

Add a default Surface to the VR Virtual Display.
authorSantos Cordon <santoscordon@google.com>
Wed, 22 Mar 2017 00:05:36 +0000 (17:05 -0700)
committerSantos Cordon <santoscordon@google.com>
Sat, 25 Mar 2017 00:37:11 +0000 (00:37 +0000)
Uses ImageReader as a do-nothing Surface to ensure the Virtual Display
gets passed through to SurfaceFlinger.

Without a Surface, the Virtual Display never makes it through the
android system to SurfaceFlinger. This change is needed to get the
Virtual Display recognized in the Flinger layers.

Test: 'setprop vr_virtualdisplay true' to enable virtual displays.
'setprop vr_debug_vd_receiver true' and shell restart for the test app.

Bug: 36491910
Change-Id: I2bf2cb4035dcf04484b7fb0bec61671864069335

services/core/java/com/android/server/vr/CompatibilityDisplay.java

index 8f95cc7..a8d6223 100644 (file)
@@ -1,4 +1,3 @@
-
 package com.android.server.vr;
 
 import static android.view.Display.INVALID_DISPLAY;
@@ -8,14 +7,18 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.ImageFormat;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.service.vr.IVrStateCallbacks;
+import android.os.SystemProperties;
+import android.service.vr.IPersistentVrStateCallbacks;
 import android.service.vr.IVrManager;
 import android.util.Log;
 import android.view.Surface;
@@ -34,11 +37,12 @@ class CompatibilityDisplay {
     private final static int HEIGHT = 1800;
     private final static int WIDTH = 1400;
     private final static int DPI = 320;
+    private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000;
 
     private final static String DEBUG_ACTION_SET_MODE =
             "com.android.server.vr.CompatibilityDisplay.SET_MODE";
     private final static String DEBUG_EXTRA_MODE_ON =
-            "com.android.servier.vr.CompatibilityDisplay.EXTRA_MODE_ON";
+            "com.android.server.vr.CompatibilityDisplay.EXTRA_MODE_ON";
     private final static String DEBUG_ACTION_SET_SURFACE =
             "com.android.server.vr.CompatibilityDisplay.SET_SURFACE";
     private final static String DEBUG_EXTRA_SURFACE =
@@ -46,14 +50,16 @@ class CompatibilityDisplay {
 
     private final DisplayManager mDisplayManager;
     private final IVrManager mVrManager;
+    private final Object mVdLock = new Object();
+    private final Handler mHandler = new Handler();
 
-    // TODO: Lock initially created when VrStateCallback was connected through Binder. This may not
-    // be necessary with the direct access to VrManager.
-    private final Object vdLock = new Object();
-
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+    /**
+     * Callback implementation to receive changes to VrMode.
+     **/
+    private final IPersistentVrStateCallbacks mVrStateCallbacks =
+            new IPersistentVrStateCallbacks.Stub() {
         @Override
-        public void onVrStateChanged(boolean enabled) {
+        public void onPersistentVrStateChanged(boolean enabled) {
             if (enabled != mIsVrModeEnabled) {
                 mIsVrModeEnabled = enabled;
                 updateVirtualDisplay();
@@ -63,7 +69,9 @@ class CompatibilityDisplay {
 
     private VirtualDisplay mVirtualDisplay;
     private Surface mSurface;
-    private boolean mIsDebugOverrideEnabled;
+    private ImageReader mImageReader;
+    private Runnable mStopVDRunnable;
+    private boolean mIsVrModeOverrideEnabled;
     private boolean mIsVrModeEnabled;
 
     public CompatibilityDisplay(DisplayManager displayManager, IVrManager vrManager) {
@@ -79,13 +87,23 @@ class CompatibilityDisplay {
         startDebugOnlyBroadcastReceiver(context);
     }
 
+    /**
+     * Creates and Destroys the virtual display depending on the current state of VrMode.
+     */
     private void updateVirtualDisplay() {
-        if (mIsVrModeEnabled || (DEBUG && mIsDebugOverrideEnabled)) {
+        boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay"));
+        if (DEBUG) {
+            Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay +
+                    ", override: " + mIsVrModeOverrideEnabled);
+        }
+
+        if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) {
             // TODO: Consider not creating the display until ActivityManager needs one on
             // which to display a 2D application.
-            // TODO: STOPSHIP Remove DEBUG conditional before launching.
-            if (DEBUG) {
+            // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching.
+            if (createVirtualDisplay) {
                 startVirtualDisplay();
+                startImageReader();
             }
         } else {
             // Stop virtual display to test exit condition
@@ -93,8 +111,17 @@ class CompatibilityDisplay {
         }
     }
 
+    /**
+     * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and
+     * set a custom Surface for the virtual display.  This allows testing of the virtual display
+     * without going into full 3D.
+     *
+     * @param context The context.
+     */
     private void startDebugOnlyBroadcastReceiver(Context context) {
-        if (DEBUG) {
+        // STOPSHIP: remove vr_debug_vd_receiver test.
+        boolean debugBroadcast = "true".equals(SystemProperties.get("vr_debug_vd_receiver"));
+        if (DEBUG || debugBroadcast) {
             IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE);
             intentFilter.addAction(DEBUG_ACTION_SET_SURFACE);
 
@@ -103,21 +130,13 @@ class CompatibilityDisplay {
                 public void onReceive(Context context, Intent intent) {
                     final String action = intent.getAction();
                     if (DEBUG_ACTION_SET_MODE.equals(action)) {
-                        mIsDebugOverrideEnabled =
+                        mIsVrModeOverrideEnabled =
                                 intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false);
                         updateVirtualDisplay();
                     } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) {
                         if (mVirtualDisplay != null) {
-                            final Surface newSurface =
-                                    intent.getParcelableExtra(DEBUG_EXTRA_SURFACE);
-
-                            Log.i(TAG, "Setting the new surface from " + mSurface + " to " + newSurface);
-                            if (newSurface != mSurface) {
-                                mVirtualDisplay.setSurface(newSurface);
-                                if (mSurface != null) {
-                                    mSurface.release();
-                                }
-                                mSurface = newSurface;
+                            if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) {
+                                setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE));
                             }
                         } else {
                             Log.w(TAG, "Cannot set the surface because the VD is null.");
@@ -128,18 +147,27 @@ class CompatibilityDisplay {
         }
     }
 
+    /**
+     * Starts listening to VrMode changes.
+     */
     private void startVrModeListener() {
         if (mVrManager != null) {
             try {
-                mVrManager.registerListener(mVrStateCallbacks);
+                mVrManager.registerPersistentVrStateListener(mVrStateCallbacks);
             } catch (RemoteException e) {
                 Log.e(TAG, "Could not register VR State listener.", e);
             }
         }
     }
 
+    /**
+     * Returns the virtual display ID if one currently exists, otherwise returns
+     * {@link INVALID_DISPLAY_ID}.
+     *
+     * @return The virtual display ID.
+     */
     public int getVirtualDisplayId() {
-        synchronized(vdLock) {
+        synchronized(mVdLock) {
             if (mVirtualDisplay != null) {
                 int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
                 if (DEBUG) {
@@ -151,6 +179,9 @@ class CompatibilityDisplay {
         return INVALID_DISPLAY;
     }
 
+    /**
+     * Starts the virtual display if one does not already exist.
+     */
     private void startVirtualDisplay() {
         if (DEBUG) {
             Log.d(TAG, "Request to start VD, DM:" + mDisplayManager);
@@ -161,7 +192,7 @@ class CompatibilityDisplay {
             return;
         }
 
-        synchronized (vdLock) {
+        synchronized (mVdLock) {
             if (mVirtualDisplay != null) {
                 Log.i(TAG, "VD already exists, ignoring request");
                 return;
@@ -169,10 +200,6 @@ class CompatibilityDisplay {
 
             mVirtualDisplay = mDisplayManager.createVirtualDisplay("VR 2D Display", WIDTH, HEIGHT,
                     DPI, null /* Surface */, 0 /* flags */);
-            if (mVirtualDisplay != null && mSurface != null && mSurface.isValid()) {
-              // TODO: Need to protect all setSurface calls with a lock.
-              mVirtualDisplay.setSurface(mSurface);
-            }
         }
 
         if (DEBUG) {
@@ -180,16 +207,69 @@ class CompatibilityDisplay {
         }
     }
 
+    /**
+     * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
+     * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
+     * of being enabled. This can happen sometimes with our 2D test app.
+     */
     private void stopVirtualDisplay() {
-        if (DEBUG) {
-            Log.i(TAG, "Santos, stopping VD");
+        if (mStopVDRunnable == null) {
+           mStopVDRunnable = new Runnable() {
+               @Override
+               public void run() {
+                    if (mIsVrModeEnabled) {
+                        Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on.");
+                    } else {
+                        Log.i(TAG, "Stopping Virtual Display");
+                        synchronized (mVdLock) {
+                            setSurfaceLocked(null); // clean up and release the surface first.
+                            if (mVirtualDisplay != null) {
+                                mVirtualDisplay.release();
+                                mVirtualDisplay = null;
+                            }
+                        }
+                    }
+               }
+           };
         }
 
-        synchronized (vdLock) {
+        mHandler.removeCallbacks(mStopVDRunnable);
+        mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS);
+    }
+
+    /**
+     * Set the surface to use with the virtual display.
+     *
+     * Code should be locked by {@link #mVdLock} before invoked.
+     *
+     * @param surface The Surface to set.
+     */
+    private void setSurfaceLocked(Surface surface) {
+        // Change the surface to either a valid surface or a null value.
+        if (mSurface != surface && (surface == null || surface.isValid())) {
+            Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface);
             if (mVirtualDisplay != null) {
-                mVirtualDisplay.release();
-                mVirtualDisplay = null;
+                mVirtualDisplay.setSurface(surface);
+            }
+            if (mSurface != null) {
+                mSurface.release();
             }
+            mSurface = surface;
+        }
+    }
+
+    /**
+     * Starts an ImageReader as a do-nothing Surface.  The virtual display will not get fully
+     * initialized within surface flinger unless it has a valid Surface associated with it. We use
+     * the ImageReader as the default valid Surface.
+     */
+    private void startImageReader() {
+        if (mImageReader == null) {
+            mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.RAW_PRIVATE,
+                2 /* maxImages */);
+        }
+        synchronized (mVdLock) {
+            setSurfaceLocked(mImageReader.getSurface());
         }
     }
 }