More work on device admins:
- You can now show a dynamic message to the user when asking to
have your DeviceAdmin added.
- A DeviceAdmin can now provide a warning message that is displayed
before a user disables it.
- Better ordering (and text) of the policy warnings.
- New API to set the maximum failed password attempts before the device
wipes itself.
- We now store the number of failed unlock attempts in persistent
storage.
- New managed dialog APIs that will be used by the settings app.
Also a little bit of cleanup as I was working on this - removed the
long unused MailboxNotAvailableException, fixed a java doc in Messenger.
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index fbd5317..ebd6f3d 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -23,19 +23,23 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.app.Activity;
import android.app.DeviceAdmin;
import android.app.DeviceAdminInfo;
import android.app.DevicePolicyManager;
import android.app.IDevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.RecoverySystem;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -64,16 +68,68 @@
ActiveAdmin mActiveAdmin;
static class ActiveAdmin {
- ActiveAdmin(DeviceAdminInfo _info) {
- info = _info;
- }
-
final DeviceAdminInfo info;
- int getUid() { return info.getActivityInfo().applicationInfo.uid; }
int passwordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
int minimumPasswordLength = 0;
long maximumTimeToUnlock = 0;
+ int maximumFailedPasswordsForWipe = 0;
+
+ ActiveAdmin(DeviceAdminInfo _info) {
+ info = _info;
+ }
+
+ int getUid() { return info.getActivityInfo().applicationInfo.uid; }
+
+ void writeToXml(XmlSerializer out)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ if (passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
+ out.startTag(null, "password-mode");
+ out.attribute(null, "value", Integer.toString(passwordMode));
+ out.endTag(null, "password-mode");
+ if (minimumPasswordLength > 0) {
+ out.startTag(null, "min-password-length");
+ out.attribute(null, "value", Integer.toString(minimumPasswordLength));
+ out.endTag(null, "mn-password-length");
+ }
+ }
+ if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
+ out.startTag(null, "max-time-to-unlock");
+ out.attribute(null, "value", Long.toString(maximumTimeToUnlock));
+ out.endTag(null, "max-time-to-unlock");
+ }
+ if (maximumFailedPasswordsForWipe != 0) {
+ out.startTag(null, "max-failed-password-wipe");
+ out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe));
+ out.endTag(null, "max-failed-password-wipe");
+ }
+ }
+
+ void readFromXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tag = parser.getName();
+ if ("password-mode".equals(tag)) {
+ passwordMode = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-length".equals(tag)) {
+ minimumPasswordLength = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("max-time-to-unlock".equals(tag)) {
+ maximumTimeToUnlock = Long.parseLong(
+ parser.getAttributeValue(null, "value"));
+ } else if ("max-failed-password-wipe".equals(tag)) {
+ maximumFailedPasswordsForWipe = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ }
+ }
+ }
}
/**
@@ -91,25 +147,42 @@
return mIPowerManager;
}
- ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
+ ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) {
+ ActiveAdmin admin = mActiveAdmin;
+ if (admin != null
+ && who.getPackageName().equals(admin.info.getActivityInfo().packageName)
+ && who.getClassName().equals(admin.info.getActivityInfo().name)) {
+ return admin;
+ }
+ return null;
+ }
+
+ ActiveAdmin getActiveAdminForCallerLocked(ComponentName who)
throws SecurityException {
- if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
+ ActiveAdmin admin = mActiveAdmin;
+ if (admin != null && admin.getUid() == Binder.getCallingUid()) {
if (who != null) {
- if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
- || !who.getClassName().equals(mActiveAdmin.info.getActivityInfo().name)) {
+ if (!who.getPackageName().equals(admin.info.getActivityInfo().packageName)
+ || !who.getClassName().equals(admin.info.getActivityInfo().name)) {
throw new SecurityException("Current admin is not " + who);
}
}
- if (!mActiveAdmin.info.usesPolicy(reqPolicy)) {
- throw new SecurityException("Admin " + mActiveAdmin.info.getComponent()
- + " did not specify uses-policy for: "
- + mActiveAdmin.info.getTagForPolicy(reqPolicy));
- }
return mActiveAdmin;
}
throw new SecurityException("Current admin is not owned by uid " + Binder.getCallingUid());
}
+ ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
+ throws SecurityException {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who);
+ if (!admin.info.usesPolicy(reqPolicy)) {
+ throw new SecurityException("Admin " + admin.info.getComponent()
+ + " did not specify uses-policy for: "
+ + admin.info.getTagForPolicy(reqPolicy));
+ }
+ return admin;
+ }
+
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
Intent intent = new Intent(action);
intent.setComponent(admin.info.getComponent());
@@ -182,25 +255,17 @@
if (ap != null) {
out.startTag(null, "admin");
out.attribute(null, "name", ap.info.getComponent().flattenToString());
- if (ap.passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
- out.startTag(null, "password-mode");
- out.attribute(null, "value", Integer.toString(ap.passwordMode));
- out.endTag(null, "password-mode");
- if (ap.minimumPasswordLength > 0) {
- out.startTag(null, "min-password-length");
- out.attribute(null, "value", Integer.toString(ap.minimumPasswordLength));
- out.endTag(null, "mn-password-length");
- }
- }
- if (ap.maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
- out.startTag(null, "max-time-to-unlock");
- out.attribute(null, "value", Long.toString(ap.maximumTimeToUnlock));
- out.endTag(null, "max-time-to-unlock");
- }
+ ap.writeToXml(out);
out.endTag(null, "admin");
}
out.endTag(null, "policies");
+ if (mFailedPasswordAttempts != 0) {
+ out.startTag(null, "failed-password-attempts");
+ out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts));
+ out.endTag(null, "failed-password-attempts");
+ }
+
out.endDocument();
stream.close();
journal.commit();
@@ -220,51 +285,41 @@
JournaledFile journal = makeJournaledFile();
FileInputStream stream = null;
File file = journal.chooseForRead();
- boolean success = false;
try {
stream = new FileInputStream(file);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, null);
- int type = parser.next();
- while (type != XmlPullParser.START_TAG) {
- type = parser.next();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
}
String tag = parser.getName();
- if ("policies".equals(tag)) {
- ActiveAdmin ap = null;
- do {
- type = parser.next();
- if (type == XmlPullParser.START_TAG) {
- tag = parser.getName();
- if (ap == null) {
- if ("admin".equals(tag)) {
- DeviceAdminInfo dai = findAdmin(
- ComponentName.unflattenFromString(
- parser.getAttributeValue(null, "name")));
- if (dai != null) {
- ap = new ActiveAdmin(dai);
- }
- }
- } else if ("password-mode".equals(tag)) {
- ap.passwordMode = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-length".equals(tag)) {
- ap.minimumPasswordLength = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("max-time-to-unlock".equals(tag)) {
- ap.maximumTimeToUnlock = Long.parseLong(
- parser.getAttributeValue(null, "value"));
- }
- } else if (type == XmlPullParser.END_TAG) {
- tag = parser.getName();
- if (ap != null && "admin".equals(tag)) {
- mActiveAdmin = ap;
- ap = null;
- }
+ if (!"policies".equals(tag)) {
+ throw new XmlPullParserException(
+ "Settings do not start with policies tag: found " + tag);
+ }
+ type = parser.next();
+ int outerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ tag = parser.getName();
+ if ("admin".equals(tag)) {
+ DeviceAdminInfo dai = findAdmin(
+ ComponentName.unflattenFromString(
+ parser.getAttributeValue(null, "name")));
+ if (dai != null) {
+ ActiveAdmin ap = new ActiveAdmin(dai);
+ ap.readFromXml(parser);
+ mActiveAdmin = ap;
}
- } while (type != XmlPullParser.END_DOCUMENT);
- success = true;
+ } else if ("failed-password-attempts".equals(tag)) {
+ mFailedPasswordAttempts = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ }
}
} catch (NullPointerException e) {
Log.w(TAG, "failed parsing " + file + " " + e);
@@ -285,10 +340,6 @@
// Ignore
}
- if (!success) {
- Log.w(TAG, "No valid start tag found in policies file");
- }
-
long timeMs = getMaximumTimeToLock();
if (timeMs <= 0) {
timeMs = Integer.MAX_VALUE;
@@ -418,6 +469,27 @@
}
}
+ public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) {
+ synchronized (this) {
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_WIPE_DATA);
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
+ if (ap.maximumFailedPasswordsForWipe != num) {
+ ap.maximumFailedPasswordsForWipe = num;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getMaximumFailedPasswordsForWipe() {
+ synchronized (this) {
+ return mActiveAdmin != null ? mActiveAdmin.maximumFailedPasswordsForWipe : 0;
+ }
+ }
+
public boolean resetPassword(String password) {
int mode;
synchronized (this) {
@@ -488,20 +560,53 @@
}
}
+ void wipeDataLocked(int flags) {
+ try {
+ RecoverySystem.rebootWipeUserData(mContext);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed requesting data wipe", e);
+ }
+ }
+
public void wipeData(int flags) {
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
getActiveAdminForCallerLocked(null,
DeviceAdminInfo.USES_POLICY_WIPE_DATA);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ wipeDataLocked(flags);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
- long ident = Binder.clearCallingIdentity();
- try {
- RecoverySystem.rebootWipeUserData(mContext);
- } catch (IOException e) {
- Log.w(TAG, "Failed requesting data wipe", e);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ }
+
+ public void getRemoveWarning(ComponentName comp, final RemoteCallback result) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(comp);
+ if (admin == null) {
+ try {
+ result.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+ Intent intent = new Intent(DeviceAdmin.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED);
+ intent.setComponent(admin.info.getComponent());
+ mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ result.sendResult(getResultExtras(false));
+ } catch (RemoteException e) {
+ }
+ }
+ }, null, Activity.RESULT_OK, null, null);
}
}
@@ -516,7 +621,10 @@
try {
mActivePasswordMode = mode;
mActivePasswordLength = length;
- mFailedPasswordAttempts = 0;
+ if (mFailedPasswordAttempts != 0) {
+ mFailedPasswordAttempts = 0;
+ saveSettingsLocked();
+ }
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
} finally {
@@ -534,6 +642,11 @@
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts++;
+ saveSettingsLocked();
+ int max = getMaximumFailedPasswordsForWipe();
+ if (max > 0 && mFailedPasswordAttempts >= max) {
+ wipeDataLocked(0);
+ }
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED,
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {
@@ -551,6 +664,7 @@
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts = 0;
+ saveSettingsLocked();
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {