New device policy to disable camera.

This introduces a new policy that a DeviceAdmin can use to disable _all_
cameras on the device. A separate CL will be made on the media side to
watch this policy bit and act accordingly.

Bug: 4185303
Change-Id: I700cfc4a8317bb74087ccae39346d74467fc58b2
diff --git a/api/current.txt b/api/current.txt
index daad5df..ac0a22e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3577,6 +3577,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
+    field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
     field public static final int USES_POLICY_EXPIRE_PASSWORD = 6; // 0x6
     field public static final int USES_POLICY_FORCE_LOCK = 3; // 0x3
     field public static final int USES_POLICY_LIMIT_PASSWORD = 0; // 0x0
@@ -3610,6 +3611,7 @@
 
   public class DevicePolicyManager {
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public boolean getCameraDisabled(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
@@ -3633,6 +3635,7 @@
     method public void lockNow();
     method public void removeActiveAdmin(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public void setCameraDisabled(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 1c7eb98..1c37414 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -130,6 +130,14 @@
      */
     public static final int USES_ENCRYPTED_STORAGE = 7;
 
+    /**
+     * A type of policy that this device admin can use: disables use of all device cameras.
+     *
+     * <p>To control this policy, the device admin must have a "disable-camera"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_DISABLE_CAMERA = 8;
+
     /** @hide */
     public static class PolicyInfo {
         public final int ident;
@@ -174,6 +182,9 @@
         sPoliciesDisplayOrder.add(new PolicyInfo(USES_ENCRYPTED_STORAGE, "encrypted-storage",
                 com.android.internal.R.string.policylab_encryptedStorage,
                 com.android.internal.R.string.policydesc_encryptedStorage));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_DISABLE_CAMERA, "disable-camera",
+                com.android.internal.R.string.policylab_disableCamera,
+                com.android.internal.R.string.policydesc_disableCamera));
 
         for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
             PolicyInfo pi = sPoliciesDisplayOrder.get(i);
@@ -365,7 +376,8 @@
      * {@link #USES_POLICY_LIMIT_PASSWORD}, {@link #USES_POLICY_WATCH_LOGIN},
      * {@link #USES_POLICY_RESET_PASSWORD}, {@link #USES_POLICY_FORCE_LOCK},
      * {@link #USES_POLICY_WIPE_DATA},
-     * {@link #USES_POLICY_EXPIRE_PASSWORD}, {@link #USES_ENCRYPTED_STORAGE}.
+     * {@link #USES_POLICY_EXPIRE_PASSWORD}, {@link #USES_ENCRYPTED_STORAGE},
+     * {@link #USES_POLICY_DISABLE_CAMERA}.
      */
     public boolean usesPolicy(int policyIdent) {
         return (mUsesPolicies & (1<<policyIdent)) != 0;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index efe2633..4147b0f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1228,6 +1228,45 @@
     }
 
     /**
+     * Called by an application that is administering the device to disable all cameras
+     * on the device.  After setting this, no applications will be able to access any cameras
+     * on the device.
+     *
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param disabled Whether or not the camera should be disabled.
+     */
+    public void setCameraDisabled(ComponentName admin, boolean disabled) {
+        if (mService != null) {
+            try {
+                mService.setCameraDisabled(admin, disabled);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Determine whether or not the device's cameras have been disabled either by the current
+     * admin, if specified, or all admins.
+     * @param admin The name of the admin component to check, or null to check if any admins
+     * have disabled the camera
+     */
+    public boolean getCameraDisabled(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getCameraDisabled(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return false;
+    }
+
+    /**
      * @hide
      */
     public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e8caca1..9419a62 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -79,6 +79,9 @@
     boolean getStorageEncryption(in ComponentName who);
     int getStorageEncryptionStatus();
 
+    void setCameraDisabled(in ComponentName who, boolean disabled);
+    boolean getCameraDisabled(in ComponentName who);
+
     void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing);
     boolean isAdminActive(in ComponentName policyReceiver);
     List<ComponentName> getActiveAdmins();
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5ea984b..3736157 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1479,6 +1479,10 @@
     <!-- Description of policy access to require encrypted storage [CHAR LIMIT=110]-->
     <string name="policydesc_encryptedStorage">Require that stored application data be encrypted
         </string>
+    <!-- Title of policy access to disable all device cameras [CHAR LIMIT=30]-->
+    <string name="policylab_disableCamera">Disable cameras</string>
+    <!-- Description of policy access to disable all device cameras [CHAR LIMIT=110]-->
+    <string name="policydesc_disableCamera">Prevent use of all device cameras</string>
 
     <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
     <!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. -->
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 3181a9d..d549308 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -172,6 +172,7 @@
         long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE;
 
         boolean encryptionRequested = false;
+        boolean disableCamera = false;
 
         // TODO: review implementation decisions with frameworks team
         boolean specifiesGlobalProxy = false;
@@ -274,6 +275,11 @@
                 out.attribute(null, "value", Boolean.toString(encryptionRequested));
                 out.endTag(null, "encryption-requested");
             }
+            if (disableCamera) {
+                out.startTag(null, "disable-camera");
+                out.attribute(null, "value", Boolean.toString(disableCamera));
+                out.endTag(null, "disable-camera");
+            }
         }
 
         void readFromXml(XmlPullParser parser)
@@ -339,6 +345,9 @@
                 } else if ("encryption-requested".equals(tag)) {
                     encryptionRequested = Boolean.parseBoolean(
                             parser.getAttributeValue(null, "value"));
+                } else if ("disable-camera".equals(tag)) {
+                    disableCamera = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, "value"));
                 } else {
                     Slog.w(TAG, "Unknown admin tag: " + tag);
                 }
@@ -393,6 +402,8 @@
             }
             pw.print(prefix); pw.print("encryptionRequested=");
                     pw.println(encryptionRequested);
+            pw.print(prefix); pw.print("disableCamera=");
+                    pw.println(disableCamera);
         }
     }
 
@@ -424,6 +435,7 @@
                 }
                 if (removed) {
                     validatePasswordOwnerLocked();
+                    syncDeviceCapabilitiesLocked();
                     saveSettingsLocked();
                 }
             }
@@ -578,6 +590,7 @@
                                 mAdminList.remove(admin);
                                 mAdminMap.remove(adminReceiver);
                                 validatePasswordOwnerLocked();
+                                syncDeviceCapabilitiesLocked();
                                 if (doProxyCleanup) {
                                     resetGlobalProxy();
                                 }
@@ -801,6 +814,7 @@
         }
 
         validatePasswordOwnerLocked();
+        syncDeviceCapabilitiesLocked();
 
         long timeMs = getMaximumTimeToLock(null);
         if (timeMs <= 0) {
@@ -844,6 +858,28 @@
         }
     }
 
+    /**
+     * Pushes down policy information to the system for any policies related to general device
+     * capabilities that need to be enforced by lower level services (e.g. Camera services).
+     */
+    void syncDeviceCapabilitiesLocked() {
+        // Ensure the status of the camera is synced down to the system. Interested native services
+        // should monitor this value and act accordingly.
+        boolean systemState = SystemProperties.getBoolean(SYSTEM_PROP_DISABLE_CAMERA, false);
+        boolean cameraDisabled = getCameraDisabled(null);
+        if (cameraDisabled != systemState) {
+            long token = Binder.clearCallingIdentity();
+            try {
+                String value = cameraDisabled ? "1" : "0";
+                Slog.v(TAG, "Change in camera state ["
+                        + SYSTEM_PROP_DISABLE_CAMERA + "] = " + value);
+                SystemProperties.set(SYSTEM_PROP_DISABLE_CAMERA, value);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
     public void systemReady() {
         synchronized (this) {
             loadSettingsLocked();
@@ -1982,6 +2018,53 @@
     private void setEncryptionRequested(boolean encrypt) {
     }
 
+    /**
+     * The system property used to share the state of the camera. The native camera service
+     * is expected to read this property and act accordingly.
+     */
+    public static final String SYSTEM_PROP_DISABLE_CAMERA = "sys.secpolicy.camera.disabled";
+
+    /**
+     * Disables all device cameras according to the specified admin.
+     */
+    public void setCameraDisabled(ComponentName who, boolean disabled) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
+            if (ap.disableCamera != disabled) {
+                ap.disableCamera = disabled;
+                saveSettingsLocked();
+            }
+            syncDeviceCapabilitiesLocked();
+        }
+    }
+
+    /**
+     * Gets whether or not all device cameras are disabled for a given admin, or disabled for any
+     * active admins.
+     */
+    public boolean getCameraDisabled(ComponentName who) {
+        synchronized (this) {
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return (admin != null) ? admin.disableCamera : false;
+            }
+
+            // Determine whether or not the device camera is disabled for any active admins.
+            final int N = mAdminList.size();
+            for (int i = 0; i < N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (admin.disableCamera) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)