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)