Add MediaProjection APIs.

The new MediaProjection infrastructure allows the system to hand out
tokens granting the ability to capture the screen's contents, audio,
etc. at a granular level. It's intended to be used both for screen
casting, via the cast APIs, as well as screen sharing via third party
applications.

The screen sharing case is implemented, but all of audio capturing
is still forthcoming.

Change-Id: I4b24669bed7083e11413c10ed8d6b025f5375316
diff --git a/Android.mk b/Android.mk
index 7a8907e..79500ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -148,6 +148,7 @@
 	core/java/android/hardware/ISerialManager.aidl \
 	core/java/android/hardware/display/IDisplayManager.aidl \
 	core/java/android/hardware/display/IDisplayManagerCallback.aidl \
+	core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl \
 	core/java/android/hardware/hdmi/IHdmiControlCallback.aidl \
 	core/java/android/hardware/hdmi/IHdmiControlService.aidl \
 	core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl \
@@ -325,6 +326,9 @@
 	media/java/android/media/routing/IMediaRouterDelegate.aidl \
 	media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \
 	media/java/android/media/routing/IMediaRouterStateCallback.aidl \
+	media/java/android/media/projection/IMediaProjection.aidl \
+	media/java/android/media/projection/IMediaProjectionCallback.aidl \
+	media/java/android/media/projection/IMediaProjectionManager.aidl \
 	media/java/android/media/session/IActiveSessionsListener.aidl \
 	media/java/android/media/session/ISessionController.aidl \
 	media/java/android/media/session/ISessionControllerCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 633d0b2..2204fbb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7191,6 +7191,7 @@
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
     field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
     field public static final java.lang.String LOCATION_SERVICE = "location";
+    field public static final java.lang.String MEDIA_PROJECTION_SERVICE = "media_projection";
     field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
     field public static final java.lang.String MEDIA_SESSION_SERVICE = "media_session";
     field public static final int MODE_APPEND = 32768; // 0x8000
@@ -13107,6 +13108,7 @@
 
   public final class DisplayManager {
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, android.view.Surface, int);
+    method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, android.view.Surface, int, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
     method public android.view.Display getDisplay(int);
     method public android.view.Display[] getDisplays();
     method public android.view.Display[] getDisplays(java.lang.String);
@@ -13116,6 +13118,7 @@
     field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
     field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
     field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
+    field public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 16; // 0x10
     field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
   }
 
@@ -13132,6 +13135,13 @@
     method public void setSurface(android.view.Surface);
   }
 
+  public static abstract class VirtualDisplay.Callbacks {
+    ctor public VirtualDisplay.Callbacks();
+    method public void onDisplayPaused();
+    method public void onDisplayResumed();
+    method public void onDisplayStopped();
+  }
+
 }
 
 package android.hardware.input {
@@ -16008,6 +16018,28 @@
 
 }
 
+package android.media.projection {
+
+  public final class MediaProjection {
+    method public void addCallback(android.media.projection.MediaProjection.Callback, android.os.Handler);
+    method public android.media.AudioRecord createAudioRecord(int, int, int, int);
+    method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, boolean, android.view.Surface, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
+    method public void removeCallback(android.media.projection.MediaProjection.Callback);
+    method public void stop();
+  }
+
+  public static abstract class MediaProjection.Callback {
+    ctor public MediaProjection.Callback();
+    method public void onStop();
+  }
+
+  public final class MediaProjectionManager {
+    method public android.media.projection.MediaProjection getMediaProjection(int, android.content.Intent);
+    method public android.content.Intent getScreenCaptureIntent();
+  }
+
+}
+
 package android.media.routing {
 
   public final class MediaRouteSelector implements android.os.Parcelable {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 739b81c..c91c90c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -192,8 +192,10 @@
     public static final int OP_MUTE_MICROPHONE = 44;
     /** @hide */
     public static final int OP_TOAST_WINDOW = 45;
+    /** @hide Capture the device's display contents and/or audio */
+    public static final int OP_PROJECT_MEDIA = 46;
     /** @hide */
-    public static final int _NUM_OP = 46;
+    public static final int _NUM_OP = 47;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION =
@@ -263,6 +265,7 @@
             OP_GET_USAGE_STATS,
             OP_MUTE_MICROPHONE,
             OP_TOAST_WINDOW,
+            OP_PROJECT_MEDIA,
     };
 
     /**
@@ -316,6 +319,7 @@
             null,
             null,
             null,
+            null,
     };
 
     /**
@@ -367,8 +371,9 @@
             "MONITOR_LOCATION",
             "MONITOR_HIGH_POWER_LOCATION",
             "GET_USAGE_STATS",
-            "OP_MUTE_MICROPHONE",
+            "MUTE_MICROPHONE",
             "TOAST_WINDOW",
+            "PROJECT_MEDIA",
     };
 
     /**
@@ -422,6 +427,7 @@
             android.Manifest.permission.PACKAGE_USAGE_STATS,
             null, // no permission for muting/unmuting microphone
             null, // no permission for displaying toasts
+            null, // no permission for projecting media
     };
 
     /**
@@ -476,6 +482,7 @@
             null, //GET_USAGE_STATS
             UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
             UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
+            null, //PROJECT_MEDIA
     };
 
     /**
@@ -527,8 +534,9 @@
             false, //MONITOR_LOCATION
             false, //MONITOR_HIGH_POWER_LOCATION
             false, //GET_USAGE_STATS
-            false, // MUTE_MICROPHONE
-            true, // TOAST_WINDOW
+            false, //MUTE_MICROPHONE
+            true, //TOAST_WINDOW
+            false, //PROJECT_MEDIA
     };
 
     /**
@@ -581,6 +589,7 @@
             AppOpsManager.MODE_IGNORED, // OP_GET_USAGE_STATS
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,
+            AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
     };
 
     /**
@@ -637,6 +646,7 @@
             false,
             false,
             false,
+            false,
     };
 
     private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cbfde14..ee6cdb7 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -72,6 +72,7 @@
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.MediaRouter;
+import android.media.projection.MediaProjectionManager;
 import android.media.session.MediaSessionManager;
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
@@ -752,6 +753,12 @@
                 return new PersistentDataBlockManager(
                         IPersistentDataBlockService.Stub.asInterface(b));
         }});
+
+        registerService(MEDIA_PROJECTION_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
+                    return new MediaProjectionManager(ctx);
+                }
+        });
     }
 
     static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1dd018f..6c7197e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2859,6 +2859,15 @@
     public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.media.projection.MediaProjectionManager} instance for managing
+     * media projection sessions.
+     * @see #getSystemService
+     * @see android.media.projection.ProjectionManager
+     */
+    public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 79673b3..ce7a2a4 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -16,7 +16,10 @@
 
 package android.hardware.display;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.media.projection.MediaProjection;
 import android.os.Handler;
 import android.util.SparseArray;
 import android.view.Display;
@@ -188,6 +191,22 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 1 << 3;
 
+
+    /**
+     * Virtual display flag: Indicates that the display is being created for
+     * the purpose of screen sharing.  This implies
+     * VIRTUAL_DISPLAY_FLAG_PRIVATE.  Other flags are not allowed (especially
+     * not VIRTUAL_DISPLAY_FLAG_PUBLIC or PRESENTATION).
+     *
+     * Requires screen share permission for use.
+     *
+     * While a display of this type exists, the system will show some sort of
+     * notification to the user indicating that the screen is being shared.
+     *
+     * @see #createVirtualDisplay
+     */
+    public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 1 << 4;
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
@@ -303,7 +322,7 @@
     }
 
     /**
-     * Unregisters an input device listener.
+     * Unregisters a display listener.
      *
      * @param listener The listener to unregister.
      *
@@ -425,6 +444,16 @@
 
     /**
      * Creates a virtual display.
+     *
+     * @see #createVirtualDisplay(String, int, int, int, Surface, int, VirtualDisplay.Callbacks)
+     */
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            int width, int height, int densityDpi, @Nullable Surface surface, int flags) {
+        return createVirtualDisplay(name, width, height, densityDpi, surface, flags, null, null);
+    }
+
+    /**
+     * Creates a virtual display.
      * <p>
      * The content of a virtual display is rendered to a {@link Surface} provided
      * by the application.
@@ -455,17 +484,28 @@
      * 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}.
+     * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+     * or {@link #VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE}.
+     * @param callbacks Callbacks to call when the state of the {@link VirtualDisplay} changes
      * @return The newly created virtual display, or null if the application could
      * not create the virtual display.
      *
      * @throws SecurityException if the caller does not have permission to create
      * a virtual display with the specified flags.
      */
-    public VirtualDisplay createVirtualDisplay(String name,
-            int width, int height, int densityDpi, Surface surface, int flags) {
-        return mGlobal.createVirtualDisplay(mContext,
-                name, width, height, densityDpi, surface, flags);
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            int width, int height, int densityDpi, @Nullable Surface surface, int flags,
+            @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+        return createVirtualDisplay(null,
+                name, width, height, densityDpi, surface, flags, callbacks, handler);
+    }
+
+    /** @hide */
+    public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
+            @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,
+            int flags, @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+        return mGlobal.createVirtualDisplay(mContext, projection,
+                name, width, height, densityDpi, surface, flags, callbacks, handler);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a8d55e8..f2426e5 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManager.DisplayListener;
+import android.media.projection.MediaProjection;
+import android.media.projection.IMediaProjection;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -368,8 +370,9 @@
         }
     }
 
-    public VirtualDisplay createVirtualDisplay(Context context, String name,
-            int width, int height, int densityDpi, Surface surface, int flags) {
+    public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
+            String name, int width, int height, int densityDpi, Surface surface, int flags,
+            VirtualDisplay.Callbacks callbacks, Handler handler) {
         if (TextUtils.isEmpty(name)) {
             throw new IllegalArgumentException("name must be non-null and non-empty");
         }
@@ -378,11 +381,12 @@
                     + "greater than 0");
         }
 
-        Binder token = new Binder();
+        VirtualDisplayCallbacks callbackWrapper = new VirtualDisplayCallbacks(callbacks, handler);
+        IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
         int displayId;
         try {
-            displayId = mDm.createVirtualDisplay(token, context.getPackageName(),
-                    name, width, height, densityDpi, surface, flags);
+            displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken,
+                    context.getPackageName(), name, width, height, densityDpi, surface, flags);
         } catch (RemoteException ex) {
             Log.e(TAG, "Could not create virtual display: " + name, ex);
             return null;
@@ -396,15 +400,15 @@
             Log.wtf(TAG, "Could not obtain display info for newly created "
                     + "virtual display: " + name);
             try {
-                mDm.releaseVirtualDisplay(token);
+                mDm.releaseVirtualDisplay(callbackWrapper);
             } catch (RemoteException ex) {
             }
             return null;
         }
-        return new VirtualDisplay(this, display, token, surface);
+        return new VirtualDisplay(this, display, callbackWrapper, surface);
     }
 
-    public void setVirtualDisplaySurface(IBinder token, Surface surface) {
+    public void setVirtualDisplaySurface(IVirtualDisplayCallbacks token, Surface surface) {
         try {
             mDm.setVirtualDisplaySurface(token, surface);
         } catch (RemoteException ex) {
@@ -412,7 +416,7 @@
         }
     }
 
-    public void releaseVirtualDisplay(IBinder token) {
+    public void releaseVirtualDisplay(IVirtualDisplayCallbacks token) {
         try {
             mDm.releaseVirtualDisplay(token);
         } catch (RemoteException ex) {
@@ -462,4 +466,59 @@
             }
         }
     }
+
+    private final static class VirtualDisplayCallbacks extends IVirtualDisplayCallbacks.Stub {
+        private VirtualDisplayCallbacksDelegate mDelegate;
+
+        public VirtualDisplayCallbacks(VirtualDisplay.Callbacks callbacks, Handler handler) {
+            mDelegate = new VirtualDisplayCallbacksDelegate(callbacks, handler);
+        }
+
+        @Override // Binder call
+        public void onDisplayPaused() {
+            mDelegate.sendEmptyMessage(VirtualDisplayCallbacksDelegate.MSG_DISPLAY_PAUSED);
+        }
+
+        @Override // Binder call
+        public void onDisplayResumed() {
+            mDelegate.sendEmptyMessage(VirtualDisplayCallbacksDelegate.MSG_DISPLAY_RESUMED);
+        }
+
+        @Override // Binder call
+        public void onDisplayStopped() {
+            mDelegate.sendEmptyMessage(VirtualDisplayCallbacksDelegate.MSG_DISPLAY_STOPPED);
+        }
+    }
+
+    private final static class VirtualDisplayCallbacksDelegate extends Handler {
+        public static final int MSG_DISPLAY_PAUSED = 0;
+        public static final int MSG_DISPLAY_RESUMED = 1;
+        public static final int MSG_DISPLAY_STOPPED = 2;
+
+        private final VirtualDisplay.Callbacks mCallbacks;
+
+        public VirtualDisplayCallbacksDelegate(VirtualDisplay.Callbacks callbacks,
+                Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
+            mCallbacks = callbacks;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mCallbacks == null) {
+                return;
+            }
+            switch (msg.what) {
+                case MSG_DISPLAY_PAUSED:
+                    mCallbacks.onDisplayPaused();
+                    break;
+                case MSG_DISPLAY_RESUMED:
+                    mCallbacks.onDisplayResumed();
+                    break;
+                case MSG_DISPLAY_STOPPED:
+                    mCallbacks.onDisplayStopped();
+                    break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 23c58c8..44ffbc4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -17,8 +17,10 @@
 package android.hardware.display;
 
 import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.IVirtualDisplayCallbacks;
 import android.hardware.display.WifiDisplay;
 import android.hardware.display.WifiDisplayStatus;
+import android.media.projection.IMediaProjection;
 import android.view.DisplayInfo;
 import android.view.Surface;
 
@@ -57,14 +59,15 @@
     // No permissions required.
     WifiDisplayStatus getWifiDisplayStatus();
 
-    // Requires CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT for certain
-    // combinations of flags.
-    int createVirtualDisplay(IBinder token, String packageName,
-            String name, int width, int height, int densityDpi, in Surface surface, int flags);
+    // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
+    // MediaProjection token for certain combinations of flags.
+    int createVirtualDisplay(in IVirtualDisplayCallbacks callbacks,
+            in IMediaProjection projectionToken, String packageName, 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);
+    void setVirtualDisplaySurface(in IVirtualDisplayCallbacks token, in Surface surface);
 
     // No permissions required but must be same Uid as the creator.
-    void releaseVirtualDisplay(in IBinder token);
+    void releaseVirtualDisplay(in IVirtualDisplayCallbacks token);
 }
diff --git a/core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl b/core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl
new file mode 100644
index 0000000..a1cdc01
--- /dev/null
+++ b/core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.display;
+
+/** @hide */
+oneway interface IVirtualDisplayCallbacks {
+    /**
+     * Called when the virtual display video projection has been
+     * paused by the system or when the surface has been detached
+     * by the application by calling setSurface(null).
+     * The surface will not receive any more buffers while paused.
+     */
+    void onDisplayPaused();
+
+    /**
+     * Called when the virtual display video projection has been
+     * resumed after having been paused.
+     */
+    void onDisplayResumed();
+
+    /**
+     * Called when the virtual display video projection has been
+     * stopped by the system.  It will no longer receive frames
+     * and it will never be resumed.  It is still the responsibility
+     * of the application to release() the virtual display.
+     */
+    void onDisplayStopped();
+}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 691d6a0..df6116b 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -35,11 +35,11 @@
 public final class VirtualDisplay {
     private final DisplayManagerGlobal mGlobal;
     private final Display mDisplay;
-    private IBinder mToken;
+    private IVirtualDisplayCallbacks mToken;
     private Surface mSurface;
 
-    VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token,
-            Surface surface) {
+    VirtualDisplay(DisplayManagerGlobal global, Display display,
+            IVirtualDisplayCallbacks token, Surface surface) {
         mGlobal = global;
         mDisplay = display;
         mToken = token;
@@ -98,4 +98,31 @@
         return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
                 + ", surface=" + mSurface + "}";
     }
+
+    /**
+     * Interface for receiving information about a {@link VirtualDisplay}'s state changes.
+     */
+    public static abstract class Callbacks {
+        /**
+         * Called when the virtual display video projection has been
+         * paused by the system or when the surface has been detached
+         * by the application by calling setSurface(null).
+         * The surface will not receive any more buffers while paused.
+         */
+         public void onDisplayPaused() { }
+
+        /**
+         * Called when the virtual display video projection has been
+         * resumed after having been paused.
+         */
+         public void onDisplayResumed() { }
+
+        /**
+         * Called when the virtual display video projection has been
+         * stopped by the system.  It will no longer receive frames
+         * and it will never be resumed.  It is still the responsibility
+         * of the application to release() the virtual display.
+         */
+        public void onDisplayStopped() { }
+    }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9fd6c4..2b313b3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2771,6 +2771,13 @@
         android:description="@string/permdesc_accessDrmCertificates"
         android:protectionLevel="signature|system" />
 
+    <!-- Api Allows an application to create media projection sessions.
+         @hide This is not a third-party API (intended for system apps). -->
+    <permission android:name="android.permission.CREATE_MEDIA_PROJECTION"
+        android:label="@string/permlab_createMediaProjection"
+        android:description="@string/permdesc_createMediaProjection"
+        android:protectionLevel="signature" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2492689..f133707 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3861,6 +3861,11 @@
     <!-- Description of an application permission that lets it control keyguard. -->
     <string name="permdesc_recovery">Allows an application to interact with the recovery system and system updates.</string>
 
+    <!-- Title of an application permission that lets it create media projection sessions. -->
+    <string name="permlab_createMediaProjection">Create media projection sessions</string>
+    <!-- Description of an application permission that lets it create media projection sessions. -->
+    <string name="permdesc_createMediaProjection">Allows an application to create media projection sessions. These sessions can provide applications the ability to capture display and audio contents. Should never be needed by normal apps.</string>
+
     <!-- Shown in the tutorial for tap twice for zoom control. -->
     <string name="tutorial_double_tap_to_zoom_message_short">Touch twice for zoom control</string>
 
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
new file mode 100644
index 0000000..dd84ad32
--- /dev/null
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.media.projection.IMediaProjectionCallback;
+
+/** {@hide} */
+interface IMediaProjection {
+    void start(IMediaProjectionCallback callback);
+    void stop();
+    boolean canProjectAudio();
+    boolean canProjectVideo();
+    boolean canProjectSecureVideo();
+    int getVirtualDisplayFlags();
+    void addCallback(IMediaProjectionCallback callback);
+    void removeCallback(IMediaProjectionCallback callback);
+}
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
new file mode 100644
index 0000000..f3743d1
--- /dev/null
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+/** {@hide} */
+oneway interface IMediaProjectionCallback {
+    void onStop();
+}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
new file mode 100644
index 0000000..6ed803a
--- /dev/null
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.media.projection.IMediaProjection;
+import android.os.IBinder;
+
+/** {@hide} */
+interface IMediaProjectionManager {
+    boolean hasProjectionPermission(int uid, String packageName);
+    IMediaProjection createProjection(int uid, String packageName, int type,
+            boolean permanentGrant);
+    boolean isValidMediaProjection(IMediaProjection projection);
+}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
new file mode 100644
index 0000000..348a577
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.AudioRecord;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.Map;
+
+/**
+ * A token granting applications the ability to capture screen contents and/or
+ * record system audio. The exact capabilities granted depend on the type of
+ * MediaProjection.
+ *
+ * <p>
+ * A screen capture session can be started through {@link
+ * MediaProjectionManager#getScreenCaptureIntent}. This grants the ability to
+ * capture screen contents, but not system audio.
+ * </p>
+ */
+public final class MediaProjection {
+    private static final String TAG = "MediaProjection";
+
+    private final IMediaProjection mImpl;
+    private final Context mContext;
+    private final Map<Callback, CallbackRecord> mCallbacks;
+
+    /** @hide */
+    public MediaProjection(Context context, IMediaProjection impl) {
+        mCallbacks = new ArrayMap<Callback, CallbackRecord>();
+        mContext = context;
+        mImpl = impl;
+        try {
+            mImpl.start(new MediaProjectionCallback());
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed to start media projection", e);
+        }
+    }
+
+    /** Register a listener to receive notifications about when the {@link
+     * MediaProjection} changes state.
+     *
+     * @param callback The callback to call.
+     * @param handler The handler on which the callback should be invoked, or
+     * null if the callback should be invoked on the calling thread's looper.
+     *
+     * @see #removeCallback
+     */
+    public void addCallback(Callback callback, Handler handler) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback should not be null");
+        }
+        mCallbacks.put(callback, new CallbackRecord(callback, handler));
+    }
+
+    /** Unregister a MediaProjection listener.
+     *
+     * @param callback The callback to unregister.
+     *
+     * @see #addCallback
+     */
+    public void removeCallback(Callback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback should not be null");
+        }
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
+     * contents of the screen.
+     *
+     * @param name The name of the virtual display, must be non-empty.
+     * @param width The width of the virtual display in pixels. Must be
+     * greater than 0.
+     * @param height The height of the virtual display in pixels. Must be
+     * greater than 0.
+     * @param dpi 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, or null if there is none initially.
+     * @param isSecure Whether the display should be considered a secure
+     * display. This typically requires special permissions not available to
+     * third party applications.
+     * @param callbacks Callbacks to call when the virtual display's state
+     * changes, or null if none.
+     * @param handler The {@link android.os.Handler} on which the callback should be
+     * invoked, or null if the callback should be invoked on the calling
+     * thread's main {@link android.os.Looper}.
+     *
+     * @see android.hardware.display.DisplayManager#createVirtualDisplay(
+     * String, int, int, int, int, Surface, VirtualDisplay.Callbacks, Handler)
+     */
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
+            @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+        DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
+        return dm.createVirtualDisplay(
+                    this, name, width, height, dpi, surface, flags, callbacks, handler);
+    }
+
+    /**
+     * Creates an AudioRecord to capture audio played back by the system.
+     */
+    public AudioRecord createAudioRecord(
+            int sampleRateInHz, int channelConfig,
+            int audioFormat, int bufferSizeInBytes) {
+        return null;
+    }
+
+    /**
+     * Stops projection.
+     */
+    public void stop() {
+        try {
+            mImpl.stop();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to stop projection", e);
+        }
+    }
+
+    /**
+     * Get the underlying IMediaProjection.
+     * @hide
+     */
+    public IMediaProjection getProjection() {
+        return mImpl;
+    }
+
+    /**
+     * Callbacks for the projection session.
+     */
+    public static abstract class Callback {
+        /**
+         * Called when the MediaProjection session is no longer valid.
+         *
+         * Once a MediaProjection has been stopped, it's up to the application to release any
+         * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s).
+         */
+        public void onStop() { }
+    }
+
+    private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
+        @Override
+        public void onStop() {
+            final int N = mCallbacks.size();
+            for (int i = 0; i < N; i++) {
+                mCallbacks.get(i).onStop();
+            }
+        }
+    }
+
+    private final static class CallbackRecord {
+        private Callback mCallback;
+        private Handler mHandler;
+
+        public CallbackRecord(Callback callback, Handler handler) {
+            mCallback = callback;
+            mHandler = handler;
+        }
+
+        public void onStop() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onStop();
+                }
+            });
+        }
+    }
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
new file mode 100644
index 0000000..aac8cf9
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.projection.IMediaProjection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Manages the retrieval of certain types of {@link MediaProjection} tokens.
+ *
+ * <p>
+ * Get an instance of this class by calling {@link
+ * android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument {@link
+ * android.content.Context#MEDIA_PROJECTION_SERVICE}.
+ * </p>
+ */
+public final class MediaProjectionManager {
+    /** @hide */
+    public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN";
+    /** @hide */
+    public static final String EXTRA_MEDIA_PROJECTION =
+            "android.media.projection.extra.EXTRA_MEDIA_PROJECTION";
+
+    /** @hide */
+    public static final int TYPE_SCREEN_CAPTURE = 0;
+    /** @hide */
+    public static final int TYPE_MIRRORING = 1;
+    /** @hide */
+    public static final int TYPE_PRESENTATION = 2;
+
+    private Context mContext;
+
+    /** @hide */
+    public MediaProjectionManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Returns an Intent that <b>must</b> passed to startActivityForResult()
+     * in order to start screen capture. The activity will prompt
+     * the user whether to allow screen capture.  The result of this
+     * activity should be passed to getMediaProjection.
+     */
+    public Intent getScreenCaptureIntent() {
+        Intent i = new Intent();
+        i.setClassName("com.android.systemui",
+                "com.android.systemui.media.MediaProjectionPermissionActivity");
+        return i;
+    }
+
+    /**
+     * Retrieve the MediaProjection obtained from a succesful screen
+     * capture request. Will be null if the result from the
+     * startActivityForResult() is anything other than RESULT_OK.
+     *
+     * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int,
+     * int, android.content.Intent)}
+     * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
+     * int, android.content.Intent)}
+     */
+    public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
+        if (resultCode != Activity.RESULT_OK || resultData == null) {
+            return null;
+        }
+        IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
+        if (projection == null) {
+            return null;
+        }
+        return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
+    }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5752646..a038899 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -109,6 +109,9 @@
 
     <uses-permission android:name="android.permission.CAMERA" />
 
+    <!-- Screen Capturing -->
+    <uses-permission android:name="android.permission.CREATE_MEDIA_PROJECTION" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
@@ -245,6 +248,15 @@
             android:taskAffinity="com.android.systemui.net"
             android:excludeFromRecents="true" />
 
+        <!-- started from MediaProjectionManager -->
+        <activity
+            android:name=".media.MediaProjectionPermissionActivity"
+            android:exported="true"
+            android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:launchMode="singleTop"
+            android:excludeFromRecents="true" />
+
         <!-- platform logo easter egg activity -->
         <activity
             android:name=".DessertCase"
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 6d101d7..c9e1618 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -9,4 +9,4 @@
 
 -keep class com.android.systemui.statusbar.phone.PhoneStatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class com.android.systemui.recents.*
\ No newline at end of file
+-keep class com.android.systemui.recents.*
diff --git a/packages/SystemUI/res/layout/remember_permission_checkbox.xml b/packages/SystemUI/res/layout/remember_permission_checkbox.xml
new file mode 100644
index 0000000..a21acb3
--- /dev/null
+++ b/packages/SystemUI/res/layout/remember_permission_checkbox.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Check box that is displayed in the activity resolver UI for the user
+     to make their selection the preferred activity. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp"
+    android:paddingTop="8dp">
+
+    <CheckBox
+        android:id="@+id/remember"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:focusable="true"
+        android:clickable="true"
+        android:text="@string/media_projection_remember_text" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f8c9d4c..46057af 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -703,5 +703,15 @@
 
     <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
     <string name="notification_hidden_text">Contents hidden</string>
+
     <string name="guest_exit_guest">Exit guest</string>
+
+    <!-- Media projection permission dialog warning text. [CHAR LIMIT=NONE] -->
+    <string name="media_projection_dialog_text"><xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g> will start capturing everything that\'s displayed on your screen.</string>
+
+    <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
+    <string name="media_projection_remember_text">Don\'t show again</string>
+
+    <!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
+    <string name="media_projection_action_text">Start now</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
new file mode 100644
index 0000000..b441eaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.media.projection.MediaProjectionManager;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+public class MediaProjectionPermissionActivity extends AlertActivity
+        implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
+    private static final String TAG = "MediaProjectionPermissionActivity";
+
+    private boolean mPermanentGrant;
+    private String mPackageName;
+    private int mUid;
+    private IMediaProjectionManager mService;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Intent intent = getIntent();
+        mPackageName = getCallingPackage();
+        IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+        mService = IMediaProjectionManager.Stub.asInterface(b);
+
+        if (mPackageName == null) {
+            finish();
+            return;
+        }
+
+        PackageManager packageManager = getPackageManager();
+        ApplicationInfo aInfo;
+        try {
+            aInfo = packageManager.getApplicationInfo(mPackageName, 0);
+            mUid = aInfo.uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "unable to look up package name", e);
+            finish();
+            return;
+        }
+
+        try {
+            if (mService.hasProjectionPermission(mUid, mPackageName)) {
+                setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName,
+                        false /*permanentGrant*/));
+                finish();
+                return;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error checking projection permissions", e);
+            finish();
+            return;
+        }
+
+        String appName = aInfo.loadLabel(packageManager).toString();
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mIcon = aInfo.loadIcon(packageManager);
+        ap.mMessage = getString(R.string.media_projection_dialog_text, appName);
+        ap.mPositiveButtonText = getString(R.string.media_projection_action_text);
+        ap.mNegativeButtonText = getString(android.R.string.cancel);
+        ap.mPositiveButtonListener = this;
+        ap.mNegativeButtonListener = this;
+
+        // add "always use" checkbox
+        LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        ap.mView = inflater.inflate(R.layout.remember_permission_checkbox, null);
+        CheckBox rememberPermissionCheckbox =
+                (CheckBox)ap.mView.findViewById(R.id.remember);
+        rememberPermissionCheckbox.setOnCheckedChangeListener(this);
+
+        setupAlert();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        try {
+            if (which == AlertDialog.BUTTON_POSITIVE) {
+                setResult(RESULT_OK, getMediaProjectionIntent(
+                        mUid, mPackageName, mPermanentGrant));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error granting projection permission", e);
+            setResult(RESULT_CANCELED);
+        } finally {
+            finish();
+        }
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        mPermanentGrant = isChecked;
+    }
+
+    private Intent getMediaProjectionIntent(int uid, String packageName, boolean permanentGrant)
+            throws RemoteException {
+        IMediaProjection projection = mService.createProjection(uid, packageName,
+                 MediaProjectionManager.TYPE_SCREEN_CAPTURE, permanentGrant);
+        Intent intent = new Intent();
+        intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
+        return intent;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4cfd042..9d1f8c6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3757,9 +3757,10 @@
 
         VirtualActivityDisplay(int width, int height, int density) {
             DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-            mVirtualDisplay = dm.createVirtualDisplay(mService.mContext, VIRTUAL_DISPLAY_BASE_NAME,
-                    width, height, density, null, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
-                    DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+            mVirtualDisplay = dm.createVirtualDisplay(mService.mContext, null,
+                    VIRTUAL_DISPLAY_BASE_NAME, width, height, density, null,
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, null, null);
 
             init(mVirtualDisplay.getDisplay());
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6697b60..2737646 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -29,8 +29,11 @@
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
 import android.hardware.display.IDisplayManager;
 import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.IVirtualDisplayCallbacks;
 import android.hardware.display.WifiDisplayStatus;
 import android.hardware.input.InputManagerInternal;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,6 +42,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.text.TextUtils;
@@ -127,6 +131,7 @@
     private final DisplayAdapterListener mDisplayAdapterListener;
     private WindowManagerInternal mWindowManagerInternal;
     private InputManagerInternal mInputManagerInternal;
+    private IMediaProjectionManager mProjectionService;
 
     // The synchronization root for the display manager.
     // This lock guards most of the display manager's state.
@@ -486,7 +491,8 @@
         }
     }
 
-    private int createVirtualDisplayInternal(IBinder appToken, int callingUid, String packageName,
+    private int createVirtualDisplayInternal(IVirtualDisplayCallbacks callbacks,
+            IMediaProjection projection, int callingUid, String packageName,
             String name, int width, int height, int densityDpi, Surface surface, int flags) {
         synchronized (mSyncRoot) {
             if (mVirtualDisplayAdapter == null) {
@@ -496,8 +502,8 @@
             }
 
             DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
-                    appToken, callingUid, packageName, name, width, height, densityDpi,
-                    surface, flags);
+                    callbacks, projection, callingUid, packageName,
+                    name, width, height, densityDpi, surface, flags);
             if (device == null) {
                 return -1;
             }
@@ -511,7 +517,7 @@
             // Something weird happened and the logical display was not created.
             Slog.w(TAG, "Rejecting request to create virtual display "
                     + "because the logical display was not created.");
-            mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+            mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callbacks.asBinder());
             handleDisplayDeviceRemovedLocked(device);
         }
         return -1;
@@ -878,6 +884,14 @@
         mTempCallbacks.clear();
     }
 
+    private IMediaProjectionManager getProjectionService() {
+        if (mProjectionService == null) {
+            IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
+            mProjectionService = IMediaProjectionManager.Stub.asInterface(b);
+        }
+        return mProjectionService;
+    }
+
     private void dumpInternal(PrintWriter pw) {
         pw.println("DISPLAY MANAGER (dumpsys display)");
 
@@ -1215,13 +1229,14 @@
         }
 
         @Override // Binder call
-        public int createVirtualDisplay(IBinder appToken, String packageName,
-                String name, int width, int height, int densityDpi, Surface surface, int flags) {
+        public int createVirtualDisplay(IVirtualDisplayCallbacks callbacks,
+                IMediaProjection projection, String packageName, String name,
+                int width, int height, int densityDpi, Surface surface, int flags) {
             final int callingUid = Binder.getCallingUid();
             if (!validatePackageName(callingUid, packageName)) {
                 throw new SecurityException("packageName must match the calling uid");
             }
-            if (appToken == null) {
+            if (callbacks == null) {
                 throw new IllegalArgumentException("appToken must not be null");
             }
             if (TextUtils.isEmpty(name)) {
@@ -1231,51 +1246,78 @@
                 throw new IllegalArgumentException("width, height, and densityDpi must be "
                         + "greater than 0");
             }
+
+            if (projection != null) {
+                try {
+                    if (!getProjectionService().isValidMediaProjection(projection)) {
+                        throw new SecurityException("Invalid media projection");
+                    }
+                } catch (RemoteException e) {
+                    throw new SecurityException("unable to validate media projection");
+                }
+                flags &= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+                try {
+                    flags |= projection.getVirtualDisplayFlags();
+                } catch (RemoteException e) {
+                    throw new RuntimeException("unable to retrieve media projection flags");
+                }
+            }
+
+            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) != 0) {
+                if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0 ||
+                        (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
+                    throw new IllegalArgumentException("screen sharing virtual displays must not "
+                            + "be public or presentation displays");
+                }
+                if (!canProjectVideo(projection)) {
+                    throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+                            + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+                            + "MediaProjection token in order to create a screen sharing virtual "
+                            + "display.");
+                }
+            }
+
+
             if (callingUid != Process.SYSTEM_UID &&
                     (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
-                if (mContext.checkCallingPermission(android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
-                        != PackageManager.PERMISSION_GRANTED
-                        && mContext.checkCallingPermission(
-                                android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
-                                != PackageManager.PERMISSION_GRANTED) {
+                if (!canProjectVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
-                            + "CAPTURE_SECURE_VIDEO_OUTPUT permission to create a "
-                            + "public virtual display.");
+                            + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+                            + "MediaProjection token to create a public virtual display.");
                 }
             }
             if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
-                if (mContext.checkCallingPermission(
-                        android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
-                        != PackageManager.PERMISSION_GRANTED) {
+                if (!canProjectSecureVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
-                            + "to create a secure virtual display.");
+                            + "or an appropriate MediaProjection token to create a "
+                            + "secure virtual display.");
                 }
             }
 
             final long token = Binder.clearCallingIdentity();
             try {
-                return createVirtualDisplayInternal(appToken, callingUid, packageName,
-                        name, width, height, densityDpi, surface, flags);
+                return createVirtualDisplayInternal(callbacks, projection, callingUid,
+                        packageName, name, width, height, densityDpi, surface, flags);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override // Binder call
-        public void setVirtualDisplaySurface(IBinder appToken, Surface surface) {
+        public void setVirtualDisplaySurface(IVirtualDisplayCallbacks callbacks, Surface surface) {
             final long token = Binder.clearCallingIdentity();
             try {
-                setVirtualDisplaySurfaceInternal(appToken, surface);
+                setVirtualDisplaySurfaceInternal(callbacks.asBinder(), surface);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override // Binder call
-        public void releaseVirtualDisplay(IBinder appToken) {
+        public void releaseVirtualDisplay(IVirtualDisplayCallbacks callbacks) {
             final long token = Binder.clearCallingIdentity();
             try {
-                releaseVirtualDisplayInternal(appToken);
+                releaseVirtualDisplayInternal(callbacks.asBinder());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1312,6 +1354,39 @@
             }
             return false;
         }
+
+        private boolean canProjectVideo(IMediaProjection projection) {
+            if (projection != null) {
+                try {
+                    if (projection.canProjectVideo()) {
+                        return true;
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to query projection service for permissions", e);
+                }
+            }
+            if (mContext.checkCallingPermission(
+                    android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            return canProjectSecureVideo(projection);
+        }
+
+        private boolean canProjectSecureVideo(IMediaProjection projection) {
+            if (projection != null) {
+                try {
+                    if (projection.canProjectSecureVideo()){
+                        return true;
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to query projection service for permissions", e);
+                }
+            }
+            return mContext.checkCallingPermission(
+                    android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
+                    != PackageManager.PERMISSION_GRANTED;
+        }
     }
 
     private final class LocalService extends DisplayManagerInternal {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index ed619d9..284780d 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -337,4 +337,4 @@
         pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
         pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ec14cf1..1032081 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -18,9 +18,13 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.IVirtualDisplayCallbacks;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionCallback;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -28,6 +32,8 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import java.io.PrintWriter;
+
 /**
  * A display adapter that provides virtual displays on behalf of applications.
  * <p>
@@ -40,30 +46,38 @@
 
     private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
             new ArrayMap<IBinder, VirtualDisplayDevice>();
+    private Handler mHandler;
 
     // Called with SyncRoot lock held.
     public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener) {
         super(syncRoot, context, handler, listener, TAG);
+        mHandler = handler;
     }
 
-    public DisplayDevice createVirtualDisplayLocked(IBinder appToken,
-            int ownerUid, String ownerPackageName,
+    public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallbacks callbacks,
+            IMediaProjection projection, int ownerUid, String ownerPackageName,
             String name, int width, int height, int densityDpi, Surface surface, int flags) {
         boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
+        IBinder appToken = callbacks.asBinder();
         IBinder displayToken = SurfaceControl.createDisplay(name, secure);
         VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
-                ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags);
+                ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags,
+                new Callbacks(callbacks, mHandler));
+
+        mVirtualDisplayDevices.put(appToken, device);
 
         try {
+            if (projection != null) {
+                projection.addCallback(new MediaProjectionCallback(appToken));
+            }
             appToken.linkToDeath(device, 0);
         } catch (RemoteException ex) {
+            mVirtualDisplayDevices.remove(appToken);
             device.destroyLocked();
             return null;
         }
 
-        mVirtualDisplayDevices.put(appToken, device);
-
         // Return the display device without actually sending the event indicating
         // that it was added.  The caller will handle it.
         return device;
@@ -98,23 +112,35 @@
         }
     }
 
-    private final class VirtualDisplayDevice extends DisplayDevice
-            implements DeathRecipient {
+    private void handleMediaProjectionStoppedLocked(IBinder appToken) {
+        VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+        if (device != null) {
+            Slog.i(TAG, "Virtual display device released because media projection stopped: "
+                    + device.mName);
+            device.stopLocked();
+        }
+    }
+
+    private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
         private final IBinder mAppToken;
         private final int mOwnerUid;
         final String mOwnerPackageName;
-        private final String mName;
+        final String mName;
         private final int mWidth;
         private final int mHeight;
         private final int mDensityDpi;
         private final int mFlags;
+        private final Callbacks mCallbacks;
 
         private Surface mSurface;
         private DisplayDeviceInfo mInfo;
+        private int mState;
+        private boolean mStopped;
 
-        public VirtualDisplayDevice(IBinder displayToken,
-                IBinder appToken, int ownerUid, String ownerPackageName,
-                String name, int width, int height, int densityDpi, Surface surface, int flags) {
+        public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
+                int ownerUid, String ownerPackageName,
+                String name, int width, int height, int densityDpi, Surface surface, int flags,
+                Callbacks callbacks) {
             super(VirtualDisplayAdapter.this, displayToken);
             mAppToken = appToken;
             mOwnerUid = ownerUid;
@@ -125,6 +151,8 @@
             mDensityDpi = densityDpi;
             mSurface = surface;
             mFlags = flags;
+            mCallbacks = callbacks;
+            mState = Display.STATE_UNKNOWN;
         }
 
         @Override
@@ -142,6 +170,19 @@
                 mSurface = null;
             }
             SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+            mCallbacks.dispatchDisplayStopped();
+        }
+
+        @Override
+        public void requestDisplayStateLocked(int state) {
+            if (state != mState) {
+                mState = state;
+                if (state == Display.STATE_OFF) {
+                    mCallbacks.dispatchDisplayPaused();
+                } else {
+                    mCallbacks.dispatchDisplayResumed();
+                }
+            }
         }
 
         @Override
@@ -150,7 +191,7 @@
         }
 
         public void setSurfaceLocked(Surface surface) {
-            if (mSurface != surface) {
+            if (!mStopped && mSurface != surface) {
                 if ((mSurface != null) != (surface != null)) {
                     sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
                 }
@@ -160,6 +201,20 @@
             }
         }
 
+        public void stopLocked() {
+            setSurfaceLocked(null);
+            mStopped = true;
+        }
+
+        @Override
+        public void dumpLocked(PrintWriter pw) {
+            super.dumpLocked(pw);
+            pw.println("mFlags=" + mFlags);
+            pw.println("mState=" + Display.stateToString(mState));
+            pw.println("mStopped=" + mStopped);
+        }
+
+
         @Override
         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
             if (mInfo == null) {
@@ -174,9 +229,11 @@
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame
                 mInfo.flags = 0;
                 if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
-                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
-                            | DisplayDeviceInfo.FLAG_NEVER_BLANK
-                            | DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
+                    if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) == 0) {
+                        mInfo.flags |=  DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY
+                                | DisplayDeviceInfo.FLAG_NEVER_BLANK;
+                    }
                 } else if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
                 }
@@ -195,4 +252,62 @@
             return mInfo;
         }
     }
+
+    private static class Callbacks extends Handler {
+        private static final int MSG_ON_DISPLAY_PAUSED = 0;
+        private static final int MSG_ON_DISPLAY_RESUMED = 1;
+        private static final int MSG_ON_DISPLAY_STOPPED = 2;
+
+        private final IVirtualDisplayCallbacks mCallbacks;
+
+        public Callbacks(IVirtualDisplayCallbacks callbacks, Handler handler) {
+            super(handler.getLooper());
+            mCallbacks = callbacks;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            try {
+                switch (msg.what) {
+                    case MSG_ON_DISPLAY_PAUSED:
+                        mCallbacks.onDisplayPaused();
+                        break;
+                    case MSG_ON_DISPLAY_RESUMED:
+                        mCallbacks.onDisplayResumed();
+                        break;
+                    case MSG_ON_DISPLAY_STOPPED:
+                        mCallbacks.onDisplayStopped();
+                        break;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
+            }
+        }
+
+        public void dispatchDisplayPaused() {
+            sendEmptyMessage(MSG_ON_DISPLAY_PAUSED);
+        }
+
+        public void dispatchDisplayResumed() {
+            sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
+        }
+
+        public void dispatchDisplayStopped() {
+            sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
+        }
+    }
+
+    private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
+        private IBinder mAppToken;
+        public MediaProjectionCallback(IBinder appToken) {
+            mAppToken = appToken;
+        }
+
+        @Override
+        public void onStop() {
+            synchronized (getSyncRoot()) {
+                handleMediaProjectionStoppedLocked(mAppToken);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
new file mode 100644
index 0000000..7b28699
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.MediaProjectionManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages MediaProjection sessions.
+ *
+ * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
+ * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
+ * grants <b>must</b> validate the token before use by calling {@link
+ * IMediaProjectionService#isValidMediaProjection}.
+ */
+public final class MediaProjectionManagerService extends SystemService
+        implements Watchdog.Monitor {
+    private static final String TAG = "MediaProjectionManagerService";
+
+    private final Object mLock = new Object(); // Protects the list of media projections
+    private final Map<IBinder, MediaProjection> mProjectionGrants;
+
+    private final Context mContext;
+    private final AppOpsManager mAppOps;
+
+    public MediaProjectionManagerService(Context context) {
+        super(context);
+        mContext = context;
+        mProjectionGrants = new ArrayMap<IBinder, MediaProjection>();
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        Watchdog.getInstance().addMonitor(this);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
+                false /*allowIsolated*/);
+    }
+
+    @Override
+    public void monitor() {
+        synchronized (mLock) { /* check for deadlock */ }
+    }
+
+    private void dump(final PrintWriter pw) {
+        pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
+        synchronized (mLock) {
+            Collection<MediaProjection> projections = mProjectionGrants.values();
+            pw.println("Media Projections: size=" + projections.size());
+            for (MediaProjection mp : projections) {
+                mp.dump(pw, "  ");
+            }
+        }
+    }
+
+    private final class BinderService extends IMediaProjectionManager.Stub {
+
+        @Override // Binder call
+        public boolean hasProjectionPermission(int uid, String packageName) {
+            long token = Binder.clearCallingIdentity();
+            boolean hasPermission = false;
+            try {
+                hasPermission |= checkPermission(packageName,
+                        android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+                        || mAppOps.checkOpNoThrow(
+                                AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
+                        == AppOpsManager.MODE_ALLOWED;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            return hasPermission;
+        }
+
+        @Override // Binder call
+        public IMediaProjection createProjection(int uid, String packageName, int type,
+                boolean isPermanentGrant) {
+            if (mContext.checkCallingPermission(Manifest.permission.CREATE_MEDIA_PROJECTION)
+                        != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires CREATE_MEDIA_PROJECTION in order to grant "
+                        + "projection permission");
+            }
+            long callingToken = Binder.clearCallingIdentity();
+            MediaProjection projection;
+            try {
+                projection = new MediaProjection(type, uid, packageName);
+                if (isPermanentGrant) {
+                    mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
+                            projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingToken);
+            }
+            return projection;
+        }
+
+        @Override // Binder call
+        public boolean isValidMediaProjection(IMediaProjection projection) {
+            return mProjectionGrants.containsKey(projection.asBinder());
+        }
+
+        @Override // Binder call
+        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+            if (mContext == null
+                    || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump MediaProjectionManager from from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+                return;
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                dump(pw);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        private boolean checkPermission(String packageName, String permission) {
+            return mContext.getPackageManager().checkPermission(permission, packageName)
+                    == PackageManager.PERMISSION_GRANTED;
+        }
+    }
+
+    private final class MediaProjection extends IMediaProjection.Stub implements DeathRecipient {
+        public int uid;
+        public String packageName;
+
+        private IBinder mToken;
+        private int mType;
+        private CallbackDelegate mCallbackDelegate;
+
+        public MediaProjection(int type, int uid, String packageName) {
+            mType = type;
+            this.uid = uid;
+            this.packageName = packageName;
+            mCallbackDelegate = new CallbackDelegate();
+        }
+
+        @Override // Binder call
+        public boolean canProjectVideo() {
+            return mType == MediaProjectionManager.TYPE_MIRRORING ||
+                    mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
+        }
+
+        @Override // Binder call
+        public boolean canProjectSecureVideo() {
+            return false;
+        }
+
+        @Override // Binder call
+        public boolean canProjectAudio() {
+            return mType == MediaProjectionManager.TYPE_MIRRORING ||
+                    mType == MediaProjectionManager.TYPE_PRESENTATION;
+        }
+
+        @Override // Binder call
+        public int getVirtualDisplayFlags() {
+            switch (mType) {
+                case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
+                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE;
+                case MediaProjectionManager.TYPE_MIRRORING:
+                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+                            DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+                case MediaProjectionManager.TYPE_PRESENTATION:
+                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
+                            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+            }
+            throw new RuntimeException("Unknown MediaProjection type");
+        }
+
+        @Override // Binder call
+        public void start(IMediaProjectionCallback callback) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback must not be null");
+            }
+            synchronized (mLock) {
+                if (mProjectionGrants.containsKey(asBinder())) {
+                    throw new IllegalStateException(
+                            "Cannot start already started MediaProjection");
+                }
+                addCallback(callback);
+                try {
+                    mToken = callback.asBinder();
+                    mToken.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    Slog.w(TAG,
+                            "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
+                    return;
+                }
+                mProjectionGrants.put(asBinder(), this);
+            }
+        }
+
+        @Override // Binder call
+        public void stop() {
+            synchronized (mLock) {
+                if (!mProjectionGrants.containsKey(asBinder())) {
+                    Slog.w(TAG, "Attempted to stop inactive MediaProjection "
+                            + "(uid=" + Binder.getCallingUid() + ", "
+                            + "pid=" + Binder.getCallingPid() + ")");
+                    return;
+                }
+                mToken.unlinkToDeath(this, 0);
+                mCallbackDelegate.dispatchStop();
+                mProjectionGrants.remove(asBinder());
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            stop();
+        }
+
+        @Override
+        public void addCallback(IMediaProjectionCallback callback) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback must not be null");
+            }
+            mCallbackDelegate.add(callback);
+        }
+
+        @Override
+        public void removeCallback(IMediaProjectionCallback callback) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback must not be null");
+            }
+            mCallbackDelegate.remove(callback);
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
+        }
+    }
+
+    private static class CallbackDelegate {
+        private static final int MSG_ON_STOP = 0;
+        private List<IMediaProjectionCallback> mCallbacks;
+        private Handler mHandler;
+        private Object mLock = new Object();
+
+        public CallbackDelegate() {
+            mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
+            mCallbacks = new ArrayList<IMediaProjectionCallback>();
+        }
+
+        public void add(IMediaProjectionCallback callback) {
+            synchronized (mLock) {
+                mCallbacks.add(callback);
+            }
+        }
+
+        public void remove(IMediaProjectionCallback callback) {
+            synchronized (mLock) {
+                mCallbacks.remove(callback);
+            }
+        }
+
+        public void dispatchStop() {
+            synchronized (mLock) {
+                for (final IMediaProjectionCallback callback : mCallbacks) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                callback.onStop();
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to notify media projection has stopped", e);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    private static String typeToString(int type) {
+        switch (type) {
+            case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
+                return "TYPE_SCREEN_CAPTURE";
+            case MediaProjectionManager.TYPE_MIRRORING:
+                return "TYPE_MIRRORING";
+            case MediaProjectionManager.TYPE_PRESENTATION:
+                return "TYPE_PRESENTATION";
+        }
+        return Integer.toString(type);
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a6030cf..65794b3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -72,6 +72,7 @@
 import com.android.server.lights.LightsService;
 import com.android.server.media.MediaRouterService;
 import com.android.server.media.MediaSessionService;
+import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.notification.NotificationManagerService;
@@ -949,6 +950,10 @@
             mSystemServiceManager.startService(LauncherAppsService.class);
         }
 
+        if (!disableNonCoreServices) {
+            mSystemServiceManager.startService(MediaProjectionManagerService.class);
+        }
+
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
         final boolean safeMode = wm.detectSafeMode();