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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user