Persist policies set through the device policy engine
Bug: 232918480
Test: manual
Change-Id: Ia7eb2e356ae470e02b669f6eb7afd8004a4c725b
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a4f4f65..9fd990f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3920,6 +3920,11 @@
*/
public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
+ /**
+ * @hide
+ */
+ public static final String PERMISSION_GRANT_POLICY_KEY = "permissionGrant";
+
// TODO: Expose this as SystemAPI once we add the query API
/**
* @hide
@@ -3928,7 +3933,7 @@
@NonNull String packageName, @NonNull String permission) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(permission);
- return "permissionGrant_" + packageName + "_" + permission;
+ return PERMISSION_GRANT_POLICY_KEY + "_" + packageName + "_" + permission;
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
new file mode 100644
index 0000000..8a8485a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.devicepolicy;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
+
+ @Override
+ void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value)
+ throws IOException {
+ serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
+ }
+
+ @Override
+ Boolean readFromXml(TypedXmlPullParser parser, String attributeName)
+ throws XmlPullParserException {
+ return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 47db0f9..15c8c27 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -19,9 +19,25 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Environment;
import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
import android.util.SparseArray;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -31,9 +47,10 @@
* admins on the device.
*/
final class DevicePolicyEngine {
- private static final String TAG = "DevicePolicyEngine";
+ static final String TAG = "DevicePolicyEngine";
private final Context mContext;
+ // TODO(b/256849338): add more granular locks
private final Object mLock = new Object();
/**
@@ -133,6 +150,7 @@
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
}
+ write();
return policyChanged;
}
}
@@ -159,6 +177,7 @@
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
}
+ write();
return policyChanged;
}
}
@@ -193,15 +212,15 @@
if (policyDefinition.isGlobalOnlyPolicy()) {
throw new IllegalArgumentException("Can't set global policy "
- + policyDefinition.getKey() + " locally.");
+ + policyDefinition.getPolicyKey() + " locally.");
}
if (!mUserPolicies.contains(userId)) {
mUserPolicies.put(userId, new HashMap<>());
}
- if (!mUserPolicies.get(userId).containsKey(policyDefinition.getKey())) {
+ if (!mUserPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
mUserPolicies.get(userId).put(
- policyDefinition.getKey(), new PolicyState<>(policyDefinition));
+ policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
return getPolicyState(mUserPolicies.get(userId), policyDefinition);
}
@@ -211,12 +230,12 @@
if (policyDefinition.isLocalOnlyPolicy()) {
throw new IllegalArgumentException("Can't set local policy "
- + policyDefinition.getKey() + " globally.");
+ + policyDefinition.getPolicyKey() + " globally.");
}
- if (!mGlobalPolicies.containsKey(policyDefinition.getKey())) {
+ if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
mGlobalPolicies.put(
- policyDefinition.getKey(), new PolicyState<>(policyDefinition));
+ policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
return getPolicyState(mGlobalPolicies, policyDefinition);
}
@@ -228,7 +247,7 @@
// we've created two policies with the same key but different types - we can only have
// stored a PolicyState of the right type.
PolicyState<V> policyState = (PolicyState<V>) policies.get(
- policyDefinition.getKey());
+ policyDefinition.getPolicyKey());
return policyState;
} catch (ClassCastException exception) {
// TODO: handle exception properly
@@ -245,4 +264,198 @@
// TODO: notify calling admin of result (e.g. success, runtime failure, policy set by
// a different admin)
}
+
+ private void write() {
+ Log.d(TAG, "Writing device policies to file.");
+ new DevicePoliciesReaderWriter().writeToFileLocked();
+ }
+
+ // TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated
+ // and could result in a different enforced policy
+ void load() {
+ Log.d(TAG, "Reading device policies from file.");
+ synchronized (mLock) {
+ clear();
+ new DevicePoliciesReaderWriter().readFromFileLocked();
+ }
+ }
+
+ private void clear() {
+ synchronized (mLock) {
+ mGlobalPolicies.clear();
+ mUserPolicies.clear();
+ }
+ }
+
+ private class DevicePoliciesReaderWriter {
+ private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+ private static final String TAG_USER_POLICY_ENTRY = "user-policy-entry";
+ private static final String TAG_DEVICE_POLICY_ENTRY = "device-policy-entry";
+ private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
+ private static final String ATTR_USER_ID = "user-id";
+ private static final String ATTR_POLICY_ID = "policy-id";
+
+ private final File mFile;
+
+ private DevicePoliciesReaderWriter() {
+ mFile = new File(Environment.getDataSystemDirectory(), DEVICE_POLICIES_XML);
+ }
+
+ void writeToFileLocked() {
+ Log.d(TAG, "Writing to " + mFile);
+
+ AtomicFile f = new AtomicFile(mFile);
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = f.startWrite();
+ TypedXmlSerializer out = Xml.resolveSerializer(outputStream);
+
+ out.startDocument(null, true);
+
+ // Actual content
+ writeInner(out);
+
+ out.endDocument();
+ out.flush();
+
+ // Commit the content.
+ f.finishWrite(outputStream);
+ outputStream = null;
+
+ } catch (IOException e) {
+ Log.e(TAG, "Exception when writing", e);
+ if (outputStream != null) {
+ f.failWrite(outputStream);
+ }
+ }
+ }
+
+ // TODO(b/256846294): Add versioning to read/write
+ void writeInner(TypedXmlSerializer serializer) throws IOException {
+ writeUserPoliciesInner(serializer);
+ writeDevicePoliciesInner(serializer);
+ }
+
+ private void writeUserPoliciesInner(TypedXmlSerializer serializer) throws IOException {
+ if (mUserPolicies != null) {
+ for (int i = 0; i < mUserPolicies.size(); i++) {
+ int userId = mUserPolicies.keyAt(i);
+ for (Map.Entry<String, PolicyState<?>> policy : mUserPolicies.get(
+ userId).entrySet()) {
+ serializer.startTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+
+ serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
+ serializer.attribute(
+ /* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
+
+ serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ policy.getValue().saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+
+ serializer.endTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+ }
+ }
+ }
+ }
+
+ private void writeDevicePoliciesInner(TypedXmlSerializer serializer) throws IOException {
+ if (mGlobalPolicies != null) {
+ for (Map.Entry<String, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
+ serializer.startTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+
+ serializer.attribute(/* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
+
+ serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ policy.getValue().saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+
+ serializer.endTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+ }
+ }
+ }
+
+ void readFromFileLocked() {
+ if (!mFile.exists()) {
+ Log.d(TAG, "" + mFile + " doesn't exist");
+ return;
+ }
+
+ Log.d(TAG, "Reading from " + mFile);
+ AtomicFile f = new AtomicFile(mFile);
+ InputStream input = null;
+ try {
+ input = f.openRead();
+ TypedXmlPullParser parser = Xml.resolvePullParser(input);
+
+ readInner(parser);
+
+ } catch (XmlPullParserException | IOException | ClassNotFoundException e) {
+ Log.e(TAG, "Error parsing resources file", e);
+ } finally {
+ IoUtils.closeQuietly(input);
+ }
+ }
+
+ private void readInner(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException, ClassNotFoundException {
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ switch (tag) {
+ case TAG_USER_POLICY_ENTRY:
+ readUserPoliciesInner(parser);
+ break;
+ case TAG_DEVICE_POLICY_ENTRY:
+ readDevicePoliciesInner(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag " + tag);
+ }
+ }
+ }
+
+ private void readUserPoliciesInner(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
+ String policyKey = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_POLICY_ID);
+ if (!mUserPolicies.contains(userId)) {
+ mUserPolicies.put(userId, new HashMap<>());
+ }
+ PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
+ if (adminsPolicy != null) {
+ mUserPolicies.get(userId).put(policyKey, adminsPolicy);
+ } else {
+ Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
+ + "AdminsPolicy.");
+ }
+ }
+
+ private void readDevicePoliciesInner(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_ID);
+ PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
+ if (adminsPolicy != null) {
+ mGlobalPolicies.put(policyKey, adminsPolicy);
+ } else {
+ Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
+ + "AdminsPolicy.");
+ }
+ }
+
+ @Nullable
+ private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) {
+ return PolicyState.readFromXml(parser);
+ }
+ Log.e(TAG, "Unknown tag " + tag);
+ }
+ Log.e(TAG, "Error parsing file, AdminsPolicy not found");
+ return null;
+ }
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index e6ef1af..f4953b4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -20,51 +20,91 @@
import android.annotation.Nullable;
import android.content.ComponentName;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.role.RoleManagerLocal;
import com.android.server.LocalManagerRegistry;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+/**
+ * {@code EnforcingAdmins} can have the following authority types:
+ *
+ * <ul>
+ * <li> {@link #DPC_AUTHORITY} meaning it's an enterprise admin (e.g. PO, DO, COPE)
+ * <li> {@link #DEVICE_ADMIN_AUTHORITY} which is a legacy non enterprise admin
+ * <li> Or a role authority, in which case {@link #mAuthorities} contains a list of all roles
+ * held by the given {@code packageName}
+ * </ul>
+ *
+ */
final class EnforcingAdmin {
static final String ROLE_AUTHORITY_PREFIX = "role:";
static final String DPC_AUTHORITY = "enterprise";
static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
static final String DEFAULT_AUTHORITY = "default";
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_CLASS_NAME = "class-name";
+ private static final String ATTR_AUTHORITIES = "authorities";
+ private static final String ATTR_AUTHORITIES_SEPARATOR = ";";
+ private static final String ATTR_USER_ID = "user-id";
+ private static final String ATTR_IS_ROLE = "is-role";
+
private final String mPackageName;
- // This is needed for DPCs and device admins
- @Nullable private final ComponentName mComponentName;
- // TODO: implement lazy loading for authorities
- private final Set<String> mAuthorities;
+ // This is needed for DPCs and active admins
+ private final ComponentName mComponentName;
+ private Set<String> mAuthorities;
+ private final int mUserId;
+ private final boolean mIsRoleAuthority;
static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
Objects.requireNonNull(packageName);
- return new EnforcingAdmin(packageName, getRoleAuthoritiesOrDefault(packageName, userId));
+ return new EnforcingAdmin(packageName, userId);
}
- static EnforcingAdmin createEnterpriseEnforcingAdmin(ComponentName componentName) {
+ static EnforcingAdmin createEnterpriseEnforcingAdmin(@NonNull ComponentName componentName) {
Objects.requireNonNull(componentName);
- return new EnforcingAdmin(componentName, Set.of(DPC_AUTHORITY));
+ return new EnforcingAdmin(
+ componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY));
}
static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName) {
Objects.requireNonNull(componentName);
- return new EnforcingAdmin(componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
+ return new EnforcingAdmin(
+ componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
}
- private EnforcingAdmin(String packageName, Set<String> authorities) {
+ private EnforcingAdmin(
+ String packageName, ComponentName componentName, Set<String> authorities) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(componentName);
+ Objects.requireNonNull(authorities);
+
+ // Role authorities should not be using this constructor
+ mIsRoleAuthority = false;
mPackageName = packageName;
- mComponentName = null;
- mAuthorities = new HashSet<>(authorities);
- }
-
- private EnforcingAdmin(ComponentName componentName, Set<String> authorities) {
- mPackageName = componentName.getPackageName();
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
+ mUserId = -1; // not needed for non role authorities
+ }
+
+ private EnforcingAdmin(String packageName, int userId) {
+ Objects.requireNonNull(packageName);
+
+ // Only role authorities use this constructor.
+ mIsRoleAuthority = true;
+ mPackageName = packageName;
+ mUserId = userId;
+ mComponentName = null;
+ // authorities will be loaded when needed
+ mAuthorities = null;
}
private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -90,8 +130,15 @@
return roles;
}
+ private Set<String> getAuthorities() {
+ if (mAuthorities == null) {
+ mAuthorities = getRoleAuthoritiesOrDefault(mPackageName, mUserId);
+ }
+ return mAuthorities;
+ }
+
boolean hasAuthority(String authority) {
- return mAuthorities.contains(authority);
+ return getAuthorities().contains(authority);
}
/**
@@ -113,24 +160,56 @@
EnforcingAdmin other = (EnforcingAdmin) o;
return Objects.equals(mPackageName, other.mPackageName)
&& Objects.equals(mComponentName, other.mComponentName)
+ && Objects.equals(mIsRoleAuthority, other.mIsRoleAuthority)
&& hasMatchingAuthorities(this, other);
}
private static boolean hasMatchingAuthorities(EnforcingAdmin admin1, EnforcingAdmin admin2) {
- if (admin1.mAuthorities.equals(admin2.mAuthorities)) {
+ if (admin1.mIsRoleAuthority && admin2.mIsRoleAuthority) {
return true;
}
- return !admin1.hasAuthority(DPC_AUTHORITY) && !admin1.hasAuthority(DEVICE_ADMIN_AUTHORITY)
- && !admin2.hasAuthority(DPC_AUTHORITY) && !admin2.hasAuthority(
- DEVICE_ADMIN_AUTHORITY);
+ return admin1.getAuthorities().equals(admin2.getAuthorities());
}
@Override
public int hashCode() {
- if (mAuthorities.contains(DPC_AUTHORITY) || mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
- return Objects.hash(mComponentName, mAuthorities);
- } else {
+ if (mIsRoleAuthority) {
+ // TODO(b/256854977): should we add UserId?
return Objects.hash(mPackageName);
+ } else {
+ return Objects.hash(mComponentName, getAuthorities());
+ }
+ }
+
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
+ serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
+ if (mIsRoleAuthority) {
+ serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
+ } else {
+ serializer.attribute(
+ /* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
+ serializer.attribute(
+ /* namespace= */ null,
+ ATTR_AUTHORITIES,
+ String.join(ATTR_AUTHORITIES_SEPARATOR, getAuthorities()));
+ }
+ }
+
+ static EnforcingAdmin readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException {
+ String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+ boolean isRoleAuthority = parser.getAttributeBoolean(/* namespace= */ null, ATTR_IS_ROLE);
+ String authoritiesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_AUTHORITIES);
+
+ if (isRoleAuthority) {
+ int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
+ return new EnforcingAdmin(packageName, userId);
+ } else {
+ String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
+ ComponentName componentName = new ComponentName(packageName, className);
+ Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
+ return new EnforcingAdmin(packageName, componentName, authorities);
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
new file mode 100644
index 0000000..3152f0b
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.devicepolicy;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+final class IntegerPolicySerializer extends PolicySerializer<Integer> {
+
+ @Override
+ void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value)
+ throws IOException {
+ serializer.attributeInt(/* namespace= */ null, attributeName, value);
+ }
+
+ @Override
+ Integer readFromXml(TypedXmlPullParser parser, String attributeName)
+ throws XmlPullParserException {
+ return parser.getAttributeInt(/* namespace= */ null, attributeName);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 2a56f2c..b22477b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -22,12 +22,19 @@
import android.content.Context;
import com.android.internal.util.function.QuadFunction;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
final class PolicyDefinition<V> {
private static final int POLICY_FLAG_NONE = 0;
+
// Only use this flag if a policy can not be applied locally.
private static final int POLICY_FLAG_GLOBAL_ONLY_POLICY = 1;
@@ -37,39 +44,66 @@
private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(false, true));
+ private static final String ATTR_POLICY_KEY = "policy-key";
+ private static final String ATTR_POLICY_DEFINITION_KEY = "policy-type-key";
+ private static final String ATTR_CALLBACK_ARGS = "callback-args";
+ private static final String ATTR_CALLBACK_ARGS_SEPARATOR = ";";
+
+
static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
DevicePolicyManager.AUTO_TIMEZONE_POLICY,
// auto timezone is enabled by default, hence disabling it is more restrictive.
FALSE_MORE_RESTRICTIVE,
POLICY_FLAG_GLOBAL_ONLY_POLICY,
(Boolean value, Context context, Integer userId, String[] args) ->
- PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context));
+ PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
+ new BooleanPolicySerializer());
+
+ // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
+ // actual permission grant policy with the correct arguments (packageName and permission name)
+ // when reading the policies from xml.
+ private static final PolicyDefinition<Integer> PERMISSION_GRANT_NO_ARGS =
+ new PolicyDefinition<>(DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY,
+ // TODO: is this really the best mechanism, what makes denied more
+ // restrictive than
+ // granted?
+ new MostRestrictive<>(
+ List.of(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT)),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::setPermissionGrantState,
+ new IntegerPolicySerializer());
static PolicyDefinition<Integer> PERMISSION_GRANT(
@NonNull String packageName, @NonNull String permission) {
- return new PolicyDefinition<>(
+ return PERMISSION_GRANT_NO_ARGS.setArgs(
DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission),
- // TODO: is this really the best mechanism, what makes denied more restrictive than
- // granted?
- new MostRestrictive<>(
- List.of(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED,
- DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
- DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT)),
- POLICY_FLAG_LOCAL_ONLY_POLICY,
- PolicyEnforcerCallbacks::setPermissionGrantState,
new String[]{packageName, permission});
}
- private final String mKey;
+ private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
+ DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
+ DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS
+ );
+
+ private final String mPolicyKey;
+ private final String mPolicyDefinitionKey;
private final ResolutionMechanism<V> mResolutionMechanism;
private final int mPolicyFlags;
// A function that accepts policy to apple, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
private final QuadFunction<V, Context, Integer, String[], Boolean> mPolicyEnforcerCallback;
private final String[] mCallbackArgs;
+ private final PolicySerializer<V> mPolicySerializer;
- String getKey() {
- return mKey;
+ private PolicyDefinition<V> setArgs(String key, String[] callbackArgs) {
+ return new PolicyDefinition<>(key, mPolicyDefinitionKey, mResolutionMechanism,
+ mPolicyFlags, mPolicyEnforcerCallback, mPolicySerializer, callbackArgs);
+ }
+
+ String getPolicyKey() {
+ return mPolicyKey;
}
/**
@@ -97,55 +131,90 @@
/**
* Callers must ensure that {@code policyType} have implemented an appropriate
- * {@link Object#equals)} implementation.
+ * {@link Object#equals} implementation.
*/
private PolicyDefinition(
String key,
ResolutionMechanism<V> resolutionMechanism,
QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
- String[] callbackArgs) {
- this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback,
- callbackArgs);
+ PolicySerializer<V> policySerializer) {
+ this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
/**
* Callers must ensure that {@code policyType} have implemented an appropriate
- * {@link Object#equals)} implementation.
- */
- private PolicyDefinition(
- String key,
- ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback) {
- this(key, resolutionMechanism, policyEnforcerCallback, /* callbackArgs= */ null);
- }
-
- /**
- * Callers must ensure that {@code policyType} have implemented an appropriate
- * {@link Object#equals)} implementation.
+ * {@link Object#equals} implementation.
*/
private PolicyDefinition(
String key,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ PolicySerializer<V> policySerializer) {
+ this(key, key, resolutionMechanism, policyFlags, policyEnforcerCallback,
+ policySerializer, /* callbackArs= */ null);
+ }
+
+ /**
+ * Callers must ensure that {@code policyType} have implemented an appropriate
+ * {@link Object#equals} implementation.
+ */
+ private PolicyDefinition(
+ String policyKey,
+ String policyDefinitionKey,
+ ResolutionMechanism<V> resolutionMechanism,
+ int policyFlags,
+ QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ PolicySerializer<V> policySerializer,
String[] callbackArgs) {
- mKey = key;
+ mPolicyKey = policyKey;
+ mPolicyDefinitionKey = policyDefinitionKey;
mResolutionMechanism = resolutionMechanism;
mPolicyFlags = policyFlags;
mPolicyEnforcerCallback = policyEnforcerCallback;
+ mPolicySerializer = policySerializer;
mCallbackArgs = callbackArgs;
+
+ // TODO: maybe use this instead of manually adding to the map
+// sPolicyDefinitions.put(policyDefinitionKey, this);
}
- /**
- * Callers must ensure that {@code policyType} have implemented an appropriate
- * {@link Object#equals)} implementation.
- */
- private PolicyDefinition(
- String key,
- ResolutionMechanism<V> resolutionMechanism,
- int policyFlags,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback) {
- this(key, resolutionMechanism, policyFlags, policyEnforcerCallback,
- /* callbackArgs= */ null);
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mPolicyKey);
+ serializer.attribute(
+ /* namespace= */ null, ATTR_POLICY_DEFINITION_KEY, mPolicyDefinitionKey);
+ if (mCallbackArgs != null) {
+ serializer.attribute(/* namespace= */ null, ATTR_CALLBACK_ARGS,
+ String.join(ATTR_CALLBACK_ARGS_SEPARATOR, mCallbackArgs));
+ }
+ }
+
+ static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser) {
+ String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
+ String policyDefinitionKey = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_POLICY_DEFINITION_KEY);
+ String callbackArgsStr = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS);
+ String[] callbackArgs = callbackArgsStr == null
+ ? null
+ : callbackArgsStr.split(ATTR_CALLBACK_ARGS_SEPARATOR);
+
+ // TODO: can we avoid casting?
+ if (callbackArgs == null) {
+ return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey);
+ } else {
+ return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey).setArgs(
+ policyKey, callbackArgs);
+ }
+ }
+
+ void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
+ throws IOException {
+ mPolicySerializer.saveToXml(serializer, attributeName, value);
+ }
+
+ V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName)
+ throws XmlPullParserException {
+ return mPolicySerializer.readFromXml(parser, attributeName);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
new file mode 100644
index 0000000..b3259d3
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.devicepolicy;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+abstract class PolicySerializer<V> {
+ abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, V value)
+ throws IOException;
+ abstract V readFromXml(TypedXmlPullParser parser, String attributeName)
+ throws XmlPullParserException;
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index a584d59..aad82cd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -18,7 +18,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Objects;
@@ -26,6 +34,11 @@
* Class containing all values set for a certain policy by different admins.
*/
final class PolicyState<V> {
+ private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
+ private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
+ private static final String ATTR_POLICY_VALUE = "policy-value";
+ private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
+
private final PolicyDefinition<V> mPolicyDefinition;
private final LinkedHashMap<EnforcingAdmin, V> mAdminsPolicy = new LinkedHashMap<>();
private V mCurrentResolvedPolicy;
@@ -34,6 +47,18 @@
mPolicyDefinition = Objects.requireNonNull(policyDefinition);
}
+ private PolicyState(
+ @NonNull PolicyDefinition<V> policyDefinition,
+ @NonNull LinkedHashMap<EnforcingAdmin, V> adminsPolicy,
+ V currentEnforcedPolicy) {
+ Objects.requireNonNull(policyDefinition);
+ Objects.requireNonNull(adminsPolicy);
+
+ mPolicyDefinition = policyDefinition;
+ mAdminsPolicy.putAll(adminsPolicy);
+ mCurrentResolvedPolicy = currentEnforcedPolicy;
+ }
+
/**
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
@@ -65,4 +90,50 @@
V getCurrentResolvedPolicy() {
return mCurrentResolvedPolicy;
}
+
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ mPolicyDefinition.saveToXml(serializer);
+
+ mPolicyDefinition.savePolicyValueToXml(
+ serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+
+ for (EnforcingAdmin admin : mAdminsPolicy.keySet()) {
+ serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
+
+ mPolicyDefinition.savePolicyValueToXml(
+ serializer, ATTR_POLICY_VALUE, mAdminsPolicy.get(admin));
+
+ serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
+ admin.saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
+
+ serializer.endTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
+ }
+ }
+
+ static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
+ LinkedHashMap<EnforcingAdmin, V> adminsPolicy = new LinkedHashMap<>();
+ V currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
+ parser, ATTR_RESOLVED_POLICY);
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) {
+ V value = policyDefinition.readPolicyValueFromXml(
+ parser, ATTR_POLICY_VALUE);
+ EnforcingAdmin admin;
+ int adminPolicyDepth = parser.getDepth();
+ if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
+ && parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
+ admin = EnforcingAdmin.readFromXml(parser);
+ adminsPolicy.put(admin, value);
+ }
+ } else {
+ Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
+ }
+ }
+ return new PolicyState<V>(policyDefinition, adminsPolicy, currentResolvedPolicy);
+ }
}