Device Owner, a special kind of device admin
A Device Owner cannot be uninstalled and is available to all users. It must
be registered before the device_provisioned flag is set.
Device admins can be disabled until used, but visible to device policy
manager, so that users wont be bothered with update requests.
Opened up a few related APIs for use by a system-installed Device Owner.
Change-Id: I847b5fe68c0f724863f778a67602b5bddc79d8e5
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 18b4ec1..d3e7c24 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -41,10 +41,14 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -62,6 +66,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.AtomicFile;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
@@ -88,10 +93,11 @@
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String DEVICE_POLICIES_XML = "device_policies.xml";
private static final String TAG = "DevicePolicyManagerService";
+ private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = 86400 * 1000;
@@ -109,6 +115,8 @@
IPowerManager mIPowerManager;
IWindowManager mIWindowManager;
+ private DeviceOwner mDeviceOwner;
+
public static class DevicePolicyData {
int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
int mActivePasswordLength = 0;
@@ -507,6 +515,7 @@
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
}
@@ -545,6 +554,14 @@
}
}
+ void loadDeviceOwner() {
+ synchronized (this) {
+ if (DeviceOwner.isRegistered()) {
+ mDeviceOwner = new DeviceOwner();
+ }
+ }
+ }
+
/**
* Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration
* reminders. Clears alarm if no expirations are configured.
@@ -709,7 +726,9 @@
Intent resolveIntent = new Intent();
resolveIntent.setComponent(adminName);
List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceivers(
- resolveIntent, PackageManager.GET_META_DATA, userHandle);
+ resolveIntent,
+ PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ userHandle);
if (infos == null || infos.size() <= 0) {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
@@ -994,6 +1013,7 @@
public void systemReady() {
synchronized (this) {
loadSettingsLocked(getUserData(UserHandle.USER_OWNER), UserHandle.USER_OWNER);
+ loadDeviceOwner();
}
}
@@ -1052,6 +1072,7 @@
}
if (replaceIndex == -1) {
policy.mAdminList.add(newAdmin);
+ enableIfNecessary(info.getPackageName(), userHandle);
} else {
policy.mAdminList.set(replaceIndex, newAdmin);
}
@@ -1119,6 +1140,11 @@
return;
}
if (admin.getUid() != Binder.getCallingUid()) {
+ // If trying to remove device owner, refuse when the caller is not the owner.
+ if (mDeviceOwner != null
+ && adminReceiver.getPackageName().equals(mDeviceOwner.getPackageName())) {
+ return;
+ }
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
}
@@ -2351,6 +2377,49 @@
}
}
+ @Override
+ public boolean setDeviceOwner(String packageName) {
+ if (packageName == null
+ || !DeviceOwner.isInstalled(packageName, mContext.getPackageManager())) {
+ throw new IllegalArgumentException("Invalid package name " + packageName
+ + " for device owner");
+ }
+ synchronized (this) {
+ if (mDeviceOwner == null && !isDeviceProvisioned()) {
+ mDeviceOwner = new DeviceOwner(packageName);
+ mDeviceOwner.writeOwnerFile();
+ return true;
+ } else {
+ throw new IllegalStateException("Trying to set device owner to " + packageName
+ + ", owner=" + mDeviceOwner.getPackageName()
+ + ", device_provisioned=" + isDeviceProvisioned());
+ }
+ }
+ }
+
+ @Override
+ public boolean isDeviceOwner(String packageName) {
+ synchronized (this) {
+ return mDeviceOwner != null
+ && mDeviceOwner.getPackageName().equals(packageName);
+ }
+ }
+
+ @Override
+ public String getDeviceOwner() {
+ synchronized (this) {
+ if (mDeviceOwner != null) {
+ return mDeviceOwner.getPackageName();
+ }
+ }
+ return null;
+ }
+
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) > 0;
+ }
+
private void enforceCrossUserPermission(int userHandle) {
if (userHandle < 0) {
throw new IllegalArgumentException("Invalid userId " + userHandle);
@@ -2364,6 +2433,22 @@
}
}
+ private void enableIfNecessary(String packageName, int userId) {
+ try {
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ ApplicationInfo ai = ipm.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ userId);
+ if (ai.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ipm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP, userId);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -2399,4 +2484,92 @@
}
}
}
+
+ static class DeviceOwner {
+ private static final String DEVICE_OWNER_XML = "device_owner.xml";
+ private static final String TAG_DEVICE_OWNER = "device-owner";
+ private static final String ATTR_PACKAGE = "package";
+ private String mPackageName;
+
+ DeviceOwner() {
+ readOwnerFile();
+ }
+
+ DeviceOwner(String packageName) {
+ this.mPackageName = packageName;
+ }
+
+ static boolean isRegistered() {
+ return new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML).exists();
+ }
+
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ static boolean isInstalled(String packageName, PackageManager pm) {
+ try {
+ PackageInfo pi;
+ if ((pi = pm.getPackageInfo(packageName, 0)) != null) {
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ }
+ } catch (NameNotFoundException nnfe) {
+ Slog.w(TAG, "Device Owner package " + packageName + " not installed.");
+ }
+ return false;
+ }
+
+ void readOwnerFile() {
+ AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML));
+ try {
+ FileInputStream input = file.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(input, null);
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+ String tag = parser.getName();
+ if (!TAG_DEVICE_OWNER.equals(tag)) {
+ throw new XmlPullParserException(
+ "Device Owner file does not start with device-owner tag: found " + tag);
+ }
+ mPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+ input.close();
+ } catch (XmlPullParserException xppe) {
+ Slog.e(TAG, "Error parsing device-owner file\n" + xppe);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "IO Exception when reading device-owner file\n" + ioe);
+ }
+ }
+
+ void writeOwnerFile() {
+ synchronized (this) {
+ writeOwnerFileLocked();
+ }
+ }
+
+ private void writeOwnerFileLocked() {
+ AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML));
+ try {
+ FileOutputStream output = file.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(output, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_DEVICE_OWNER);
+ out.attribute(null, ATTR_PACKAGE, mPackageName);
+ out.endTag(null, TAG_DEVICE_OWNER);
+ out.endDocument();
+ out.flush();
+ file.finishWrite(output);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "IO Exception when writing device-owner file\n" + ioe);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 09daf56..c4b98ad 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -1425,9 +1425,9 @@
if (accountType == null) throw new IllegalArgumentException("accountType is null");
checkManageAccountsPermission();
- // Is user allowed to modify accounts?
- if (!getUserManager().getUserRestrictions(Binder.getCallingUserHandle())
- .getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+ // Is user disallowed from modifying accounts?
+ if (getUserManager().getUserRestrictions(Binder.getCallingUserHandle())
+ .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
try {
response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
"User is not allowed to add an account!");
@@ -2572,7 +2572,7 @@
if (callingUid != android.os.Process.myUid()) {
Bundle restrictions = getUserManager().getUserRestrictions(
new UserHandle(UserHandle.getUserId(callingUid)));
- if (!restrictions.getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+ if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
return false;
}
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index f1af634..f7ed9d7 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -5933,7 +5933,7 @@
null);
final int uid = Binder.getCallingUid();
- if (!isUserAllowed(UserHandle.getUserId(uid), UserManager.ALLOW_INSTALL_APPS)) {
+ if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
try {
observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
@@ -5981,7 +5981,7 @@
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"installExistingPackage for user " + userId);
}
- if (!isUserAllowed(userId, UserManager.ALLOW_INSTALL_APPS)) {
+ if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
}
@@ -6015,13 +6015,13 @@
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isUserAllowed(int userId, String restrictionKey) {
+ private boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = sUserManager.getUserRestrictions(userId);
- if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
- Log.w(TAG, "User does not have permission to: " + restrictionKey);
- return false;
+ if (restrictions.getBoolean(restrictionKey, false)) {
+ Log.w(TAG, "User is restricted: " + restrictionKey);
+ return true;
}
- return true;
+ return false;
}
@Override
@@ -8418,7 +8418,7 @@
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"deletePackage for user " + userId);
}
- if (!isUserAllowed(userId, UserManager.ALLOW_UNINSTALL_APPS)) {
+ if (isUserRestricted(userId, UserManager.DISALLOW_UNINSTALL_APPS)) {
try {
observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
@@ -8464,7 +8464,8 @@
IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
- if (dpm != null && dpm.packageHasActiveAdmins(packageName, userId)) {
+ if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId)
+ || dpm.isDeviceOwner(packageName))) {
Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
}
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 636b0e5..fecc2df 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -226,6 +226,13 @@
}
}
+ @Override
+ public boolean isRestricted() {
+ synchronized (mPackagesLock) {
+ return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+ }
+ }
+
/*
* Should be locked on mUsers before calling this.
*/
@@ -558,7 +565,6 @@
mNextSerialNumber = MIN_USER_ID;
Bundle restrictions = new Bundle();
- initRestrictionsToDefaults(restrictions);
mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
updateUserIdsLocked();
@@ -608,11 +614,11 @@
Bundle restrictions = mUserRestrictions.get(userInfo.id);
if (restrictions != null) {
serializer.startTag(null, TAG_RESTRICTIONS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_CONFIG_WIFI);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_MODIFY_ACCOUNTS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_INSTALL_APPS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_UNINSTALL_APPS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_CONFIG_LOCATION_ACCESS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
serializer.endTag(null, TAG_RESTRICTIONS);
}
serializer.endTag(null, TAG_USER);
@@ -676,7 +682,6 @@
long lastLoggedInTime = 0L;
boolean partial = false;
Bundle restrictions = new Bundle();
- initRestrictionsToDefaults(restrictions);
FileInputStream fis = null;
try {
@@ -725,11 +730,11 @@
name = parser.getText();
}
} else if (TAG_RESTRICTIONS.equals(tag)) {
- readBoolean(parser, restrictions, UserManager.ALLOW_CONFIG_WIFI);
- readBoolean(parser, restrictions, UserManager.ALLOW_MODIFY_ACCOUNTS);
- readBoolean(parser, restrictions, UserManager.ALLOW_INSTALL_APPS);
- readBoolean(parser, restrictions, UserManager.ALLOW_UNINSTALL_APPS);
- readBoolean(parser, restrictions, UserManager.ALLOW_CONFIG_LOCATION_ACCESS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
}
}
}
@@ -758,7 +763,9 @@
private void readBoolean(XmlPullParser parser, Bundle restrictions,
String restrictionKey) {
String value = parser.getAttributeValue(null, restrictionKey);
- restrictions.putBoolean(restrictionKey, value == null ? true : Boolean.parseBoolean(value));
+ if (value != null) {
+ restrictions.putBoolean(restrictionKey, Boolean.parseBoolean(value));
+ }
}
private void writeBoolean(XmlSerializer xml, Bundle restrictions, String restrictionKey)
@@ -769,14 +776,6 @@
}
}
- private void initRestrictionsToDefaults(Bundle restrictions) {
- restrictions.putBoolean(UserManager.ALLOW_CONFIG_WIFI, true);
- restrictions.putBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS, true);
- restrictions.putBoolean(UserManager.ALLOW_INSTALL_APPS, true);
- restrictions.putBoolean(UserManager.ALLOW_UNINSTALL_APPS, true);
- restrictions.putBoolean(UserManager.ALLOW_CONFIG_LOCATION_ACCESS, true);
- }
-
private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
String valueString = parser.getAttributeValue(null, attr);
if (valueString == null) return defaultValue;
@@ -823,7 +822,6 @@
writeUserLocked(userInfo);
updateUserIdsLocked();
Bundle restrictions = new Bundle();
- initRestrictionsToDefaults(restrictions);
mUserRestrictions.append(userId, restrictions);
}
}