Add support for dynamically setting the virtual display surface.

Previously, the surface that backs a virtual display had to be set
at the time when the display was created.  This change now makes
it possible to set or remove the surface later.  The virtual display
is treated as if it were "off" while no surface is attached to it.

Change-Id: Ib4fdbbb8b4ee79f0fb9ceb648f9bda4a8fa6a2ca
diff --git a/api/current.txt b/api/current.txt
index 554ef8a..a3824d7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10950,7 +10950,9 @@
 
   public final class VirtualDisplay {
     method public android.view.Display getDisplay();
+    method public android.view.Surface getSurface();
     method public void release();
+    method public void setSurface(android.view.Surface);
   }
 
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a517bc5..79673b3 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -437,6 +437,14 @@
      * The behavior of the virtual display depends on the flags that are provided
      * to this method.  By default, virtual displays are created to be private,
      * non-presentation and unsecure.  Permissions may be required to use certain flags.
+     * </p><p>
+     * As of {@link android.os.Build.VERSION_CODES#KITKAT_WATCH}, the surface may
+     * be attached or detached dynamically using {@link VirtualDisplay#setSurface}.
+     * Previously, the surface had to be non-null when {@link #createVirtualDisplay}
+     * was called and could not be changed for the lifetime of the display.
+     * </p><p>
+     * Detaching the surface that backs a virtual display has a similar effect to
+     * turning off the screen.
      * </p>
      *
      * @param name The name of the virtual display, must be non-empty.
@@ -444,7 +452,7 @@
      * @param height The height of the virtual display in pixels, must be greater than 0.
      * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
      * @param surface The surface to which the content of the virtual display should
-     * be rendered, must be non-null.
+     * be rendered, or null if there is none initially.
      * @param flags A combination of virtual display flags:
      * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
      * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, or {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3417430..a8d55e8 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -377,9 +377,6 @@
             throw new IllegalArgumentException("width, height, and densityDpi must be "
                     + "greater than 0");
         }
-        if (surface == null) {
-            throw new IllegalArgumentException("surface must not be null");
-        }
 
         Binder token = new Binder();
         int displayId;
@@ -404,7 +401,15 @@
             }
             return null;
         }
-        return new VirtualDisplay(this, display, token);
+        return new VirtualDisplay(this, display, token, surface);
+    }
+
+    public void setVirtualDisplaySurface(IBinder token, Surface surface) {
+        try {
+            mDm.setVirtualDisplaySurface(token, surface);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to set virtual display surface.", ex);
+        }
     }
 
     public void releaseVirtualDisplay(IBinder token) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 68eb13f..23c58c8 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -63,5 +63,8 @@
             String name, int width, int height, int densityDpi, in Surface surface, int flags);
 
     // No permissions required but must be same Uid as the creator.
+    void setVirtualDisplaySurface(in IBinder token, in Surface surface);
+
+    // No permissions required but must be same Uid as the creator.
     void releaseVirtualDisplay(in IBinder token);
 }
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 01e5bac..691d6a0 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -17,15 +17,18 @@
 
 import android.os.IBinder;
 import android.view.Display;
+import android.view.Surface;
 
 /**
  * Represents a virtual display. The content of a virtual display is rendered to a
  * {@link android.view.Surface} that you must provide to {@link DisplayManager#createVirtualDisplay
  * createVirtualDisplay()}.
- * <p>Because a virtual display renders to a surface provided by the application, it will be
+ * <p>
+ * Because a virtual display renders to a surface provided by the application, it will be
  * released automatically when the process terminates and all remaining windows on it will
- * be forcibly removed. However, you should also explicitly call {@link #release} when you're
- * done with it.
+ * be forcibly removed.  However, you should also explicitly call {@link #release} when
+ * you're done with it.
+ * </p>
  *
  * @see DisplayManager#createVirtualDisplay
  */
@@ -33,11 +36,14 @@
     private final DisplayManagerGlobal mGlobal;
     private final Display mDisplay;
     private IBinder mToken;
+    private Surface mSurface;
 
-    VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token) {
+    VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token,
+            Surface surface) {
         mGlobal = global;
         mDisplay = display;
         mToken = token;
+        mSurface = surface;
     }
 
     /**
@@ -48,6 +54,32 @@
     }
 
     /**
+     * Gets the surface that backs the virtual display.
+     */
+    public Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Sets the surface that backs the virtual display.
+     * <p>
+     * Detaching the surface that backs a virtual display has a similar effect to
+     * turning off the screen.
+     * </p><p>
+     * It is still the caller's responsibility to destroy the surface after it has
+     * been detached.
+     * </p>
+     *
+     * @param surface The surface to set, or null to detach the surface from the virtual display.
+     */
+    public void setSurface(Surface surface) {
+        if (mSurface != surface) {
+            mGlobal.setVirtualDisplaySurface(mToken, surface);
+            mSurface = surface;
+        }
+    }
+
+    /**
      * Releases the virtual display and destroys its underlying surface.
      * <p>
      * All remaining windows on the virtual display will be forcibly removed
@@ -63,6 +95,7 @@
 
     @Override
     public String toString() {
-        return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken + "}";
+        return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
+                + ", surface=" + mSurface + "}";
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 071417b..6697b60 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -517,6 +517,16 @@
         return -1;
     }
 
+    private void setVirtualDisplaySurfaceInternal(IBinder appToken, Surface surface) {
+        synchronized (mSyncRoot) {
+            if (mVirtualDisplayAdapter == null) {
+                return;
+            }
+
+            mVirtualDisplayAdapter.setVirtualDisplaySurfaceLocked(appToken, surface);
+        }
+    }
+
     private void releaseVirtualDisplayInternal(IBinder appToken) {
         synchronized (mSyncRoot) {
             if (mVirtualDisplayAdapter == null) {
@@ -1221,9 +1231,6 @@
                 throw new IllegalArgumentException("width, height, and densityDpi must be "
                         + "greater than 0");
             }
-            if (surface == null) {
-                throw new IllegalArgumentException("surface must not be null");
-            }
             if (callingUid != Process.SYSTEM_UID &&
                     (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
                 if (mContext.checkCallingPermission(android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
@@ -1255,6 +1262,16 @@
         }
 
         @Override // Binder call
+        public void setVirtualDisplaySurface(IBinder appToken, Surface surface) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setVirtualDisplaySurfaceInternal(appToken, surface);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void releaseVirtualDisplay(IBinder appToken) {
             final long token = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 95ca0d2..a165f26 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -69,6 +69,13 @@
         return device;
     }
 
+    public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
+        VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+        if (device != null) {
+            device.setSurfaceLocked(surface);
+        }
+    }
+
     public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
         VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
         if (device != null) {
@@ -144,6 +151,17 @@
             }
         }
 
+        public void setSurfaceLocked(Surface surface) {
+            if (mSurface != surface) {
+                if ((mSurface != null) != (surface != null)) {
+                    sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+                }
+                sendTraversalRequestLocked();
+                mSurface = surface;
+                mInfo = null;
+            }
+        }
+
         @Override
         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
             if (mInfo == null) {
@@ -171,6 +189,7 @@
                 }
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+                mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
             }