Files
frameworks_base/services/java/com/android/server/DevicePolicyManagerService.java
Dianne Hackborn 8aa2e8939c More device admin work: description, policy control.
There is now a description attribute associated with all components,
that can supply user-visible information about what the component does.
We use this to show such information about device admins, and wallpapers
are also updated to be able to show this in addition to the existing
description in their meta-data.

This also defines security control for admins, requiring that they
declare the policies they will touch, and enforcing that they do
so to be able to use various APIs.
2010-01-26 12:28:15 -08:00

563 lines
21 KiB
Java

/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import com.android.common.FastXmlSerializer;
import com.android.internal.widget.LockPatternUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.app.DeviceAdmin;
import android.app.DeviceAdminInfo;
import android.app.DevicePolicyManager;
import android.app.IDevicePolicyManager;
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.IBinder;
import android.os.IPowerManager;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.Xml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
/**
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String TAG = "DevicePolicyManagerService";
private final Context mContext;
IPowerManager mIPowerManager;
int mActivePasswordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
int mActivePasswordLength = 0;
int mFailedPasswordAttempts = 0;
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;
}
/**
* Instantiates the service.
*/
public DevicePolicyManagerService(Context context) {
mContext = context;
}
private IPowerManager getIPowerManager() {
if (mIPowerManager == null) {
IBinder b = ServiceManager.getService(Context.POWER_SERVICE);
mIPowerManager = IPowerManager.Stub.asInterface(b);
}
return mIPowerManager;
}
ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
throws SecurityException {
if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
if (who != null) {
if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
|| !who.getClassName().equals(mActiveAdmin.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());
}
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
Intent intent = new Intent(action);
intent.setComponent(admin.info.getComponent());
mContext.sendBroadcast(intent);
}
void sendAdminCommandLocked(String action, int reqPolicy) {
if (mActiveAdmin != null) {
if (mActiveAdmin.info.usesPolicy(reqPolicy)) {
return;
}
sendAdminCommandLocked(mActiveAdmin, action);
}
}
ComponentName getActiveAdminLocked() {
if (mActiveAdmin != null) {
return mActiveAdmin.info.getComponent();
}
return null;
}
void removeActiveAdminLocked(ComponentName adminReceiver) {
ComponentName cur = getActiveAdminLocked();
if (cur != null && cur.equals(adminReceiver)) {
sendAdminCommandLocked(mActiveAdmin,
DeviceAdmin.ACTION_DEVICE_ADMIN_DISABLED);
// XXX need to wait for it to complete.
mActiveAdmin = null;
}
}
public DeviceAdminInfo findAdmin(ComponentName adminName) {
Intent resolveIntent = new Intent();
resolveIntent.setComponent(adminName);
List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceivers(
resolveIntent, PackageManager.GET_META_DATA);
if (infos == null || infos.size() <= 0) {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
try {
return new DeviceAdminInfo(mContext, infos.get(0));
} catch (XmlPullParserException e) {
Log.w(TAG, "Bad device admin requested: " + adminName, e);
return null;
} catch (IOException e) {
Log.w(TAG, "Bad device admin requested: " + adminName, e);
return null;
}
}
private static JournaledFile makeJournaledFile() {
final String base = "/data/system/device_policies.xml";
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
private void saveSettingsLocked() {
JournaledFile journal = makeJournaledFile();
FileOutputStream stream = null;
try {
stream = new FileOutputStream(journal.chooseForWrite(), false);
XmlSerializer out = new FastXmlSerializer();
out.setOutput(stream, "utf-8");
out.startDocument(null, true);
out.startTag(null, "policies");
ActiveAdmin ap = mActiveAdmin;
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");
}
out.endTag(null, "admin");
}
out.endTag(null, "policies");
out.endDocument();
stream.close();
journal.commit();
} catch (IOException e) {
try {
if (stream != null) {
stream.close();
}
} catch (IOException ex) {
// Ignore
}
journal.rollback();
}
}
private void loadSettingsLocked() {
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();
}
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;
}
}
} while (type != XmlPullParser.END_DOCUMENT);
success = true;
}
} catch (NullPointerException e) {
Log.w(TAG, "failed parsing " + file + " " + e);
} catch (NumberFormatException e) {
Log.w(TAG, "failed parsing " + file + " " + e);
} catch (XmlPullParserException e) {
Log.w(TAG, "failed parsing " + file + " " + e);
} catch (IOException e) {
Log.w(TAG, "failed parsing " + file + " " + e);
} catch (IndexOutOfBoundsException e) {
Log.w(TAG, "failed parsing " + file + " " + e);
}
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// Ignore
}
if (!success) {
Log.w(TAG, "No valid start tag found in policies file");
}
long timeMs = getMaximumTimeToLock();
if (timeMs <= 0) {
timeMs = Integer.MAX_VALUE;
}
try {
getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
} catch (RemoteException e) {
Log.w(TAG, "Failure talking with power manager", e);
}
}
public void systemReady() {
synchronized (this) {
loadSettingsLocked();
}
}
public void setActiveAdmin(ComponentName adminReceiver) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
DeviceAdminInfo info = findAdmin(adminReceiver);
if (info == null) {
throw new IllegalArgumentException("Bad admin: " + adminReceiver);
}
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
ComponentName cur = getActiveAdminLocked();
if (cur != null && cur.equals(adminReceiver)) {
throw new IllegalStateException("An admin is already set");
}
if (cur != null) {
removeActiveAdminLocked(adminReceiver);
}
mActiveAdmin = new ActiveAdmin(info);
saveSettingsLocked();
sendAdminCommandLocked(mActiveAdmin,
DeviceAdmin.ACTION_DEVICE_ADMIN_ENABLED);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
public ComponentName getActiveAdmin() {
synchronized (this) {
return getActiveAdminLocked();
}
}
public void removeActiveAdmin(ComponentName adminReceiver) {
synchronized (this) {
if (mActiveAdmin == null || mActiveAdmin.getUid() != Binder.getCallingUid()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
}
long ident = Binder.clearCallingIdentity();
try {
removeActiveAdminLocked(adminReceiver);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
public void setPasswordMode(ComponentName who, int mode) {
synchronized (this) {
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.passwordMode != mode) {
ap.passwordMode = mode;
saveSettingsLocked();
}
}
}
public int getPasswordMode() {
synchronized (this) {
return mActiveAdmin != null ? mActiveAdmin.passwordMode
: DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
}
}
public void setMinimumPasswordLength(ComponentName who, int length) {
synchronized (this) {
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordLength != length) {
ap.minimumPasswordLength = length;
saveSettingsLocked();
}
}
}
public int getMinimumPasswordLength() {
synchronized (this) {
return mActiveAdmin != null ? mActiveAdmin.minimumPasswordLength : 0;
}
}
public boolean isActivePasswordSufficient() {
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_LIMIT_PASSWORD);
return mActivePasswordMode >= getPasswordMode()
&& mActivePasswordLength >= getMinimumPasswordLength();
}
}
public int getCurrentFailedPasswordAttempts() {
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_WATCH_LOGIN);
return mFailedPasswordAttempts;
}
}
public boolean resetPassword(String password) {
int mode;
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_RESET_PASSWORD);
mode = getPasswordMode();
if (password.length() < getMinimumPasswordLength()) {
return false;
}
}
// Don't do this with the lock held, because it is going to call
// back in to the service.
long ident = Binder.clearCallingIdentity();
try {
LockPatternUtils utils = new LockPatternUtils(mContext);
utils.saveLockPassword(password, mode);
} finally {
Binder.restoreCallingIdentity(ident);
}
return true;
}
public void setMaximumTimeToLock(ComponentName who, long timeMs) {
synchronized (this) {
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_UNLOCK);
if (ap.maximumTimeToUnlock != timeMs) {
ap.maximumTimeToUnlock = timeMs;
long ident = Binder.clearCallingIdentity();
try {
saveSettingsLocked();
if (timeMs <= 0) {
timeMs = Integer.MAX_VALUE;
}
try {
getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
} catch (RemoteException e) {
Log.w(TAG, "Failure talking with power manager", e);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
public long getMaximumTimeToLock() {
synchronized (this) {
return mActiveAdmin != null ? mActiveAdmin.maximumTimeToUnlock : 0;
}
}
public void lockNow() {
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_FORCE_LOCK);
// STOPSHIP need to implement.
}
}
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 {
RecoverySystem.rebootWipeUserData(mContext);
} catch (IOException e) {
Log.w(TAG, "Failed requesting data wipe", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
public void setActivePasswordState(int mode, int length) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
synchronized (this) {
if (mActivePasswordMode != mode || mActivePasswordLength != length
|| mFailedPasswordAttempts != 0) {
long ident = Binder.clearCallingIdentity();
try {
mActivePasswordMode = mode;
mActivePasswordLength = length;
mFailedPasswordAttempts = 0;
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
public void reportFailedPasswordAttempt() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts++;
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED,
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
public void reportSuccessfulPasswordAttempt() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
synchronized (this) {
if (mFailedPasswordAttempts != 0) {
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts = 0;
sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
}