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.
This commit is contained in:
Dianne Hackborn
2010-01-26 18:01:04 -08:00
parent 2fc1f4a029
commit 8ea138cbf1
14 changed files with 715 additions and 224 deletions

View File

@@ -23,19 +23,23 @@ import org.xmlpull.v1.XmlPullParser;
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// 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 class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts = 0;
saveSettingsLocked();
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {