summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt26
-rw-r--r--core/java/android/app/admin/PolicyUpdateReason.java74
-rw-r--r--core/java/android/app/admin/PolicyUpdatesReceiver.java315
-rw-r--r--core/java/android/app/admin/TargetUser.java85
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java258
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java33
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java80
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java9
10 files changed, 824 insertions, 86 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 326a8e781a60..e89881c2f008 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8004,6 +8004,26 @@ package android.app.admin {
field public static final int PACKAGE_POLICY_BLOCKLIST = 1; // 0x1
}
+ public final class PolicyUpdateReason {
+ ctor public PolicyUpdateReason(int);
+ method public int getReasonCode();
+ field public static final int REASON_CONFLICTING_ADMIN_POLICY = 0; // 0x0
+ field public static final int REASON_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public abstract class PolicyUpdatesReceiver extends android.content.BroadcastReceiver {
+ ctor public PolicyUpdatesReceiver();
+ method public void onPolicyChanged(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, @NonNull android.app.admin.PolicyUpdateReason);
+ method public void onPolicySetResult(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, int, @Nullable android.app.admin.PolicyUpdateReason);
+ method public final void onReceive(android.content.Context, android.content.Intent);
+ field public static final String ACTION_DEVICE_POLICY_CHANGED = "android.app.admin.action.DEVICE_POLICY_CHANGED";
+ field public static final String ACTION_DEVICE_POLICY_SET_RESULT = "android.app.admin.action.DEVICE_POLICY_SET_RESULT";
+ field public static final String EXTRA_PACKAGE_NAME = "android.app.admin.extra.PACKAGE_NAME";
+ field public static final String EXTRA_PERMISSION_NAME = "android.app.admin.extra.PERMISSION_NAME";
+ field public static final int POLICY_SET_RESULT_FAILURE = -1; // 0xffffffff
+ field public static final int POLICY_SET_RESULT_SUCCESS = 0; // 0x0
+ }
+
public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
method public int describeContents();
method @NonNull public int[] getExcludedUids();
@@ -8132,6 +8152,12 @@ package android.app.admin {
field public static final int ERROR_UNKNOWN = 1; // 0x1
}
+ public final class TargetUser {
+ field @NonNull public static final android.app.admin.TargetUser GLOBAL;
+ field @NonNull public static final android.app.admin.TargetUser LOCAL_USER;
+ field @NonNull public static final android.app.admin.TargetUser PARENT_USER;
+ }
+
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<java.lang.Integer> getReasons();
diff --git a/core/java/android/app/admin/PolicyUpdateReason.java b/core/java/android/app/admin/PolicyUpdateReason.java
new file mode 100644
index 000000000000..97d282dbc8d7
--- /dev/null
+++ b/core/java/android/app/admin/PolicyUpdateReason.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing the reason a policy (set from {@link DevicePolicyManager}) hasn't been enforced
+ * (passed in to {@link PolicyUpdatesReceiver#onPolicySetResult}) or has changed (passed in to
+ * {@link PolicyUpdatesReceiver#onPolicyChanged}).
+ */
+public final class PolicyUpdateReason {
+
+ /**
+ * Reason code to indicate that the policy has not been enforced or has changed for an unknown
+ * reason.
+ */
+ public static final int REASON_UNKNOWN = -1;
+
+ /**
+ * Reason code to indicate that the policy has not been enforced or has changed because another
+ * admin has set a conflicting policy on the device.
+ */
+ public static final int REASON_CONFLICTING_ADMIN_POLICY = 0;
+
+ /**
+ * Reason codes for {@link #getReasonCode()}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "REASON_" }, value = {
+ REASON_UNKNOWN,
+ REASON_CONFLICTING_ADMIN_POLICY,
+ })
+ public @interface ReasonCode {}
+
+ private final int mReasonCode;
+
+ /**
+ * Constructor for {@code PolicyUpdateReason} that takes in a reason code describing why the
+ * policy has changed.
+ *
+ * @param reasonCode Describes why the policy has changed.
+ */
+ public PolicyUpdateReason(@ReasonCode int reasonCode) {
+ this.mReasonCode = reasonCode;
+ }
+
+ /**
+ * Returns reason code for why a policy hasn't been applied or has changed.
+ */
+ @ReasonCode
+ public int getReasonCode() {
+ return mReasonCode;
+ }
+}
diff --git a/core/java/android/app/admin/PolicyUpdatesReceiver.java b/core/java/android/app/admin/PolicyUpdatesReceiver.java
new file mode 100644
index 000000000000..ff30a5f8a037
--- /dev/null
+++ b/core/java/android/app/admin/PolicyUpdatesReceiver.java
@@ -0,0 +1,315 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.BroadcastBehavior;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+// TODO(b/261432333): Add more detailed javadocs on using DeviceAdminService.
+/**
+ * Base class for implementing a policy update receiver. This class provides a convenience for
+ * interpreting the raw intent actions ({@link #ACTION_DEVICE_POLICY_SET_RESULT} and
+ * {@link #ACTION_DEVICE_POLICY_CHANGED}) that are sent by the system.
+ *
+ * <p>The callback methods happen on the main thread of the process. Thus, long-running
+ * operations must be done on another thread.
+ *
+ * <p>When publishing your {@code PolicyUpdatesReceiver} subclass as a receiver, it must
+ * require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission.
+ */
+public abstract class PolicyUpdatesReceiver extends BroadcastReceiver {
+ private static String TAG = "PolicyUpdatesReceiver";
+
+ /**
+ * Result code passed in to {@link #onPolicySetResult} to indicate that the policy has been
+ * set successfully.
+ */
+ public static final int POLICY_SET_RESULT_SUCCESS = 0;
+
+ /**
+ * Result code passed in to {@link #onPolicySetResult} to indicate that the policy has NOT been
+ * set, a {@link PolicyUpdateReason} will be passed in to {@link #onPolicySetResult} to indicate
+ * the reason.
+ */
+ public static final int POLICY_SET_RESULT_FAILURE = -1;
+
+ /**
+ * Result codes passed in to {@link #onPolicySetResult}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "POLICY_SET_RESULT_" }, value = {
+ POLICY_SET_RESULT_SUCCESS,
+ POLICY_SET_RESULT_FAILURE,
+ })
+ public @interface ResultCode {}
+
+ /**
+ * Action for a broadcast sent to admins to communicate back the result of setting a policy in
+ * {@link DevicePolicyManager}.
+ *
+ * <p>Admins wishing to receive these updates (via {@link #onPolicySetResult}) should include
+ * this action in the intent filter for their receiver in the manifest, the receiver
+ * must be protected by {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that
+ * only the system can send updates.
+ *
+ * <p>Admins shouldn't implement {@link #onReceive} and should instead implement
+ * {@link #onPolicySetResult}.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_DEVICE_POLICY_SET_RESULT =
+ "android.app.admin.action.DEVICE_POLICY_SET_RESULT";
+
+ /**
+ * Action for a broadcast sent to admins to communicate back a change in a policy they have
+ * previously set.
+ *
+ * <p>Admins wishing to receive these updates should include this action in the intent filter
+ * for their receiver in the manifest, the receiver must be protected by
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that only the system can
+ * send updates.
+ *
+ * <p>Admins shouldn't implement {@link #onReceive} and should instead implement
+ * {@link #onPolicyChanged}.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_DEVICE_POLICY_CHANGED =
+ "android.app.admin.action.DEVICE_POLICY_CHANGED";
+
+ // TODO(b/264510719): Remove once API linter is fixed
+ @SuppressLint("ActionValue")
+ /**
+ * A string extra holding the package name the policy applies to, (see
+ * {@link PolicyUpdatesReceiver#onPolicyChanged} and
+ * {@link PolicyUpdatesReceiver#onPolicySetResult})
+ */
+ public static final String EXTRA_PACKAGE_NAME =
+ "android.app.admin.extra.PACKAGE_NAME";
+
+ // TODO(b/264510719): Remove once API linter is fixed
+ @SuppressLint("ActionValue")
+ /**
+ * A string extra holding the permission name the policy applies to, (see
+ * {@link PolicyUpdatesReceiver#onPolicyChanged} and
+ * {@link PolicyUpdatesReceiver#onPolicySetResult})
+ */
+ public static final String EXTRA_PERMISSION_NAME =
+ "android.app.admin.extra.PERMISSION_NAME";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_CHANGED_KEY =
+ "android.app.admin.extra.POLICY_CHANGED_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_KEY = "android.app.admin.extra.POLICY_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_BUNDLE_KEY =
+ "android.app.admin.extra.POLICY_BUNDLE_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_SET_RESULT_KEY =
+ "android.app.admin.extra.POLICY_SET_RESULT_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_UPDATE_REASON_KEY =
+ "android.app.admin.extra.POLICY_UPDATE_REASON_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_TARGET_USER_ID =
+ "android.app.admin.extra.POLICY_TARGET_USER_ID";
+
+ /**
+ * Intercept standard policy update broadcasts. Implementations should not override this
+ * method and rely on the callbacks instead.
+ *
+ * @hide
+ */
+ @Override
+ public final void onReceive(Context context, Intent intent) {
+ Objects.requireNonNull(intent.getAction());
+ switch (intent.getAction()) {
+ case ACTION_DEVICE_POLICY_SET_RESULT:
+ Log.i(TAG, "Received ACTION_DEVICE_POLICY_SET_RESULT");
+ onPolicySetResult(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ getTargetUser(intent), getPolicyResult(intent), getFailureReason(intent));
+ break;
+ case ACTION_DEVICE_POLICY_CHANGED:
+ Log.i(TAG, "Received ACTION_DEVICE_POLICY_CHANGED");
+ onPolicyChanged(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ getTargetUser(intent), getPolicyChangedReason(intent));
+ break;
+ default:
+ Log.e(TAG, "Unknown action received: " + intent.getAction());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ static String getPolicyKey(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_KEY)) {
+ throw new IllegalArgumentException("PolicyKey has to be provided.");
+ }
+ return intent.getStringExtra(EXTRA_POLICY_KEY);
+ }
+
+ /**
+ * @hide
+ */
+ @ResultCode
+ static int getPolicyResult(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_SET_RESULT_KEY)) {
+ throw new IllegalArgumentException("Result has to be provided.");
+ }
+ return intent.getIntExtra(EXTRA_POLICY_SET_RESULT_KEY, POLICY_SET_RESULT_FAILURE);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ static Bundle getPolicyExtraBundle(Intent intent) {
+ Bundle bundle = intent.getBundleExtra(EXTRA_POLICY_BUNDLE_KEY);
+ return bundle == null ? new Bundle() : bundle;
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ static PolicyUpdateReason getFailureReason(Intent intent) {
+ if (getPolicyResult(intent) != POLICY_SET_RESULT_FAILURE) {
+ return null;
+ }
+ return getPolicyChangedReason(intent);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ static PolicyUpdateReason getPolicyChangedReason(Intent intent) {
+ int reasonCode = intent.getIntExtra(
+ EXTRA_POLICY_UPDATE_REASON_KEY, PolicyUpdateReason.REASON_UNKNOWN);
+ return new PolicyUpdateReason(reasonCode);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ static TargetUser getTargetUser(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_TARGET_USER_ID)) {
+ throw new IllegalArgumentException("TargetUser has to be provided.");
+ }
+ int targetUserId = intent.getIntExtra(
+ EXTRA_POLICY_TARGET_USER_ID, TargetUser.LOCAL_USER_ID);
+ return new TargetUser(targetUserId);
+ }
+
+ // TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
+ /**
+ * Callback triggered after an admin has set a policy using one of the APIs in
+ * {@link DevicePolicyManager} to notify the admin whether it has been successful or not.
+ *
+ * <p>Admins wishing to receive this callback should include
+ * {@link PolicyUpdatesReceiver#ACTION_DEVICE_POLICY_SET_RESULT} in the intent filter for their
+ * receiver in the manifest, the receiver must be protected by
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that only the system can
+ * send updates.
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param policyKey Key to identify which policy this callback relates to.
+ * @param additionalPolicyParams Bundle containing additional params that may be required to
+ * identify some of the policy
+ * (e.g. {@link PolicyUpdatesReceiver#EXTRA_PACKAGE_NAME}
+ * and {@link PolicyUpdatesReceiver#EXTRA_PERMISSION_NAME}).
+ * Each policy will document the required additional params if
+ * needed.
+ * @param targetUser The {@link TargetUser} which this policy relates to.
+ * @param result Indicates whether the policy has been set successfully,
+ * (see {@link PolicyUpdatesReceiver#POLICY_SET_RESULT_SUCCESS} and
+ * {@link PolicyUpdatesReceiver#POLICY_SET_RESULT_FAILURE}).
+ * @param reason Indicates the reason the policy failed to apply, {@code null} if the policy was
+ * applied successfully.
+ */
+ public void onPolicySetResult(
+ @NonNull Context context,
+ @NonNull String policyKey,
+ @NonNull Bundle additionalPolicyParams,
+ @NonNull TargetUser targetUser,
+ @ResultCode int result,
+ @Nullable PolicyUpdateReason reason) {}
+
+ // TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
+ // TODO(b/261430877): Add javadocs to explain when will this get triggered
+ /**
+ * Callback triggered when a policy previously set by the admin has changed.
+ *
+ * <p>Admins wishing to receive this callback should include
+ * {@link PolicyUpdatesReceiver#ACTION_DEVICE_POLICY_CHANGED} in the intent filter for their
+ * receiver in the manifest, the receiver must be protected by
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that only the system can
+ * send updates.
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param policyKey Key to identify which policy this callback relates to.
+ * @param additionalPolicyParams Bundle containing additional params that may be required to
+ * identify some of the policy
+ * (e.g. {@link PolicyUpdatesReceiver#EXTRA_PACKAGE_NAME}
+ * and {@link PolicyUpdatesReceiver#EXTRA_PERMISSION_NAME}).
+ * Each policy will document the required additional params if
+ * needed.
+ * @param targetUser The {@link TargetUser} which this policy relates to.
+ * @param reason Indicates the reason the policy value has changed.
+ */
+ public void onPolicyChanged(
+ @NonNull Context context,
+ @NonNull String policyKey,
+ @NonNull Bundle additionalPolicyParams,
+ @NonNull TargetUser targetUser,
+ @NonNull PolicyUpdateReason reason) {}
+}
diff --git a/core/java/android/app/admin/TargetUser.java b/core/java/android/app/admin/TargetUser.java
new file mode 100644
index 000000000000..acbac29dabe6
--- /dev/null
+++ b/core/java/android/app/admin/TargetUser.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Class representing the target user of a policy set by an admin
+ * (set from {@link DevicePolicyManager}), this is passed in to
+ * {@link PolicyUpdatesReceiver#onPolicySetResult} and
+ * {@link PolicyUpdatesReceiver#onPolicyChanged}.
+ */
+public final class TargetUser {
+ /**
+ * @hide
+ */
+ public static final int LOCAL_USER_ID = -1;
+
+ /**
+ * @hide
+ */
+ public static final int PARENT_USER_ID = -2;
+
+ /**
+ * @hide
+ */
+ public static final int GLOBAL_USER_ID = -3;
+
+ /**
+ * Indicates that the policy relates to the user the admin is installed on.
+ */
+ @NonNull
+ public static final TargetUser LOCAL_USER = new TargetUser(LOCAL_USER_ID);
+
+ /**
+ * For admins of profiles, this indicates that the policy relates to the parent profile.
+ */
+ @NonNull
+ public static final TargetUser PARENT_USER = new TargetUser(PARENT_USER_ID);
+
+ /**
+ * This indicates the policy is a global policy.
+ */
+ @NonNull
+ public static final TargetUser GLOBAL = new TargetUser(GLOBAL_USER_ID);
+
+ private final int mUserId;
+
+ /**
+ * @hide
+ */
+ public TargetUser(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TargetUser other = (TargetUser) o;
+ return mUserId == other.mUserId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserId);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 15c8c2705e6c..d796ddf7a462 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,9 +16,25 @@
package com.android.server.devicepolicy;
+import static android.app.admin.PolicyUpdateReason.REASON_CONFLICTING_ADMIN_POLICY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_SET_RESULT_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_TARGET_USER_ID;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_REASON_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_FAILURE;
+import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_SUCCESS;
+
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.PolicyUpdatesReceiver;
+import android.app.admin.TargetUser;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.util.AtomicFile;
@@ -39,8 +55,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -56,7 +74,7 @@ final class DevicePolicyEngine {
/**
* Map of <userId, Map<policyKey, policyState>>
*/
- private final SparseArray<Map<String, PolicyState<?>>> mUserPolicies;
+ private final SparseArray<Map<String, PolicyState<?>>> mLocalPolicies;
/**
* Map of <policyKey, policyState>
@@ -65,7 +83,7 @@ final class DevicePolicyEngine {
DevicePolicyEngine(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
- mUserPolicies = new SparseArray<>();
+ mLocalPolicies = new SparseArray<>();
mGlobalPolicies = new HashMap<>();
}
@@ -92,8 +110,28 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
if (policyChanged) {
- enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ enforcePolicy(
+ policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+
}
+ boolean wasAdminPolicyEnforced = Objects.equals(
+ policyState.getCurrentResolvedPolicy(), value);
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+
+ write();
return policyChanged;
}
}
@@ -117,12 +155,28 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
+ boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ TargetUser.GLOBAL_USER_ID);
}
+ boolean wasAdminPolicyEnforced = Objects.equals(
+ policyState.getCurrentResolvedPolicy(), value);
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ TargetUser.GLOBAL_USER_ID);
+
+ write();
return policyChanged;
}
}
@@ -148,8 +202,26 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
- enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ enforcePolicy(
+ policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
}
+ // for a remove policy to be enforced, it means no current policy exists
+ boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+
write();
return policyChanged;
}
@@ -176,7 +248,23 @@ final class DevicePolicyEngine {
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
+
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ TargetUser.GLOBAL_USER_ID);
}
+ // for a remove policy to be enforced, it means no current policy exists
+ boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ TargetUser.GLOBAL_USER_ID);
+
write();
return policyChanged;
}
@@ -215,19 +303,18 @@ final class DevicePolicyEngine {
+ policyDefinition.getPolicyKey() + " locally.");
}
- if (!mUserPolicies.contains(userId)) {
- mUserPolicies.put(userId, new HashMap<>());
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
}
- if (!mUserPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
- mUserPolicies.get(userId).put(
+ if (!mLocalPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
+ mLocalPolicies.get(userId).put(
policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
- return getPolicyState(mUserPolicies.get(userId), policyDefinition);
+ return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
}
@NonNull
private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
-
if (policyDefinition.isLocalOnlyPolicy()) {
throw new IllegalArgumentException("Can't set local policy "
+ policyDefinition.getPolicyKey() + " globally.");
@@ -260,9 +347,104 @@ final class DevicePolicyEngine {
// TODO: null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
policyDefinition.enforcePolicy(policyValue, mContext, userId);
- // TODO: send broadcast or call callback to notify admins of policy change
- // TODO: notify calling admin of result (e.g. success, runtime failure, policy set by
- // a different admin)
+ }
+
+ private <V> void sendPolicyResultToAdmin(
+ EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, boolean success,
+ int reason, int targetUserId) {
+ Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
+ intent.setPackage(admin.getPackageName());
+
+ List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ admin.getUserId());
+ if (receivers.isEmpty()) {
+ Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_SET_RESULT"
+ + "in package " + admin.getPackageName());
+ return;
+ }
+
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
+ extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
+
+ if (policyDefinition.getCallbackArgs() != null
+ && !policyDefinition.getCallbackArgs().isEmpty()) {
+ extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
+ }
+ extras.putInt(
+ EXTRA_POLICY_SET_RESULT_KEY,
+ success ? POLICY_SET_RESULT_SUCCESS : POLICY_SET_RESULT_FAILURE);
+
+ if (!success) {
+ extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
+ }
+
+ intent.putExtras(extras);
+ maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
+ }
+
+ // TODO(b/261430877): Finalise the decision on which admins to send the updates to.
+ private <V> void sendPolicyChangedToAdmins(
+ Set<EnforcingAdmin> admins, EnforcingAdmin callingAdmin,
+ PolicyDefinition<V> policyDefinition,
+ int targetUserId) {
+ for (EnforcingAdmin admin: admins) {
+ // We're sending a separate broadcast for the calling admin with the result.
+ if (admin.equals(callingAdmin)) {
+ continue;
+ }
+ maybeSendOnPolicyChanged(
+ admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, targetUserId);
+ }
+ }
+
+ private <V> void maybeSendOnPolicyChanged(
+ EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int reason,
+ int targetUserId) {
+ Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_CHANGED);
+ intent.setPackage(admin.getPackageName());
+
+ List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ admin.getUserId());
+ if (receivers.isEmpty()) {
+ Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_CHANGED"
+ + "in package " + admin.getPackageName());
+ return;
+ }
+
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
+ extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
+
+ if (policyDefinition.getCallbackArgs() != null
+ && !policyDefinition.getCallbackArgs().isEmpty()) {
+ extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
+ }
+ extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
+ intent.putExtras(extras);
+ maybeSendIntentToAdminReceivers(
+ intent, UserHandle.of(admin.getUserId()), receivers);
+ }
+
+ private void maybeSendIntentToAdminReceivers(
+ Intent intent, UserHandle userHandle, List<ResolveInfo> receivers) {
+ for (ResolveInfo resolveInfo : receivers) {
+ if (!Manifest.permission.BIND_DEVICE_ADMIN.equals(
+ resolveInfo.activityInfo.permission)) {
+ Log.w(TAG, "Receiver " + resolveInfo.activityInfo + " is not protected by"
+ + "BIND_DEVICE_ADMIN permission!");
+ continue;
+ }
+ // TODO: If admins are always bound to, do I still need to set
+ // "BroadcastOptions.setBackgroundActivityStartsAllowed"?
+ // TODO: maybe protect it with a permission that is granted to the role so that we
+ // don't accidentally send a broadcast to an admin that no longer holds the role.
+ mContext.sendBroadcastAsUser(intent, userHandle);
+ }
}
private void write() {
@@ -283,14 +465,14 @@ final class DevicePolicyEngine {
private void clear() {
synchronized (mLock) {
mGlobalPolicies.clear();
- mUserPolicies.clear();
+ mLocalPolicies.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_LOCAL_POLICY_ENTRY = "local-policy-entry";
+ private static final String TAG_GLOBAL_POLICY_ENTRY = "global-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";
@@ -332,17 +514,17 @@ final class DevicePolicyEngine {
// TODO(b/256846294): Add versioning to read/write
void writeInner(TypedXmlSerializer serializer) throws IOException {
- writeUserPoliciesInner(serializer);
- writeDevicePoliciesInner(serializer);
+ writeLocalPoliciesInner(serializer);
+ writeGlobalPoliciesInner(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(
+ private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
+ if (mLocalPolicies != null) {
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ int userId = mLocalPolicies.keyAt(i);
+ for (Map.Entry<String, PolicyState<?>> policy : mLocalPolicies.get(
userId).entrySet()) {
- serializer.startTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
serializer.attribute(
@@ -352,16 +534,16 @@ final class DevicePolicyEngine {
policy.getValue().saveToXml(serializer);
serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
- serializer.endTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
}
}
}
}
- private void writeDevicePoliciesInner(TypedXmlSerializer serializer) throws IOException {
+ private void writeGlobalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
if (mGlobalPolicies != null) {
for (Map.Entry<String, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
- serializer.startTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
serializer.attribute(/* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
@@ -369,7 +551,7 @@ final class DevicePolicyEngine {
policy.getValue().saveToXml(serializer);
serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
- serializer.endTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
}
}
}
@@ -402,11 +584,11 @@ final class DevicePolicyEngine {
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
switch (tag) {
- case TAG_USER_POLICY_ENTRY:
- readUserPoliciesInner(parser);
+ case TAG_LOCAL_POLICY_ENTRY:
+ readLocalPoliciesInner(parser);
break;
- case TAG_DEVICE_POLICY_ENTRY:
- readDevicePoliciesInner(parser);
+ case TAG_GLOBAL_POLICY_ENTRY:
+ readGlobalPoliciesInner(parser);
break;
default:
Log.e(TAG, "Unknown tag " + tag);
@@ -414,24 +596,24 @@ final class DevicePolicyEngine {
}
}
- private void readUserPoliciesInner(TypedXmlPullParser parser)
+ private void readLocalPoliciesInner(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<>());
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
}
PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
if (adminsPolicy != null) {
- mUserPolicies.get(userId).put(policyKey, adminsPolicy);
+ mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
} else {
Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
+ "AdminsPolicy.");
}
}
- private void readDevicePoliciesInner(TypedXmlPullParser parser)
+ private void readGlobalPoliciesInner(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_ID);
PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8c2065e7f764..657e45b99eda 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8082,7 +8082,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
PolicyDefinition.AUTO_TIMEZONE,
// TODO(b/260573124): add correct enforcing admin when permission changes are
// merged.
- EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ caller.getComponentName(), caller.getUserId()),
enabled);
} else {
mInjector.binderWithCleanCallingIdentity(() ->
@@ -12599,7 +12600,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (isCoexistenceEnabled(caller)) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ who, caller.getUserId());
if (packages.length == 0) {
mDevicePolicyEngine.removeLocalPolicy(
PolicyDefinition.LOCK_TASK,
@@ -12619,7 +12621,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
- EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, caller.getUserId()),
policy,
caller.getUserId());
}
@@ -12715,7 +12717,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
}
if (isCoexistenceEnabled(caller)) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle);
LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
PolicyDefinition.LOCK_TASK,
caller.getUserId()).getPoliciesSetByAdmins().get(admin);
@@ -12728,7 +12730,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
- EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
policy,
caller.getUserId());
} else {
@@ -14362,7 +14364,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// TODO(b/260573124): Add correct enforcing admin when permission changes are
// merged, and don't forget to handle delegates! Enterprise admins assume
// component name isn't null.
- EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ caller.getComponentName(), caller.getUserId()),
grantState,
caller.getUserId());
// TODO: update javadoc to reflect that callback no longer return success/failure
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 9261d59ce1e3..00e48eb67ab0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -69,16 +69,18 @@ final class EnforcingAdmin {
return new EnforcingAdmin(packageName, userId);
}
- static EnforcingAdmin createEnterpriseEnforcingAdmin(@NonNull ComponentName componentName) {
+ static EnforcingAdmin createEnterpriseEnforcingAdmin(
+ @NonNull ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY));
+ componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
}
- static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName) {
+ static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
+ componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
+ userId);
}
static String getRoleAuthorityOf(String roleName) {
@@ -86,7 +88,7 @@ final class EnforcingAdmin {
}
private EnforcingAdmin(
- String packageName, ComponentName componentName, Set<String> authorities) {
+ String packageName, ComponentName componentName, Set<String> authorities, int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(componentName);
Objects.requireNonNull(authorities);
@@ -96,7 +98,7 @@ final class EnforcingAdmin {
mPackageName = packageName;
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
- mUserId = -1; // not needed for non role authorities
+ mUserId = userId;
}
private EnforcingAdmin(String packageName, int userId) {
@@ -145,6 +147,15 @@ final class EnforcingAdmin {
return getAuthorities().contains(authority);
}
+ @NonNull
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ int getUserId() {
+ return mUserId;
+ }
+
/**
* For two EnforcingAdmins to be equal they must:
*
@@ -188,11 +199,11 @@ final class EnforcingAdmin {
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.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
+ if (!mIsRoleAuthority) {
serializer.attribute(
/* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
+ // Role authorities get recomputed on load so no need to save them.
serializer.attribute(
/* namespace= */ null,
ATTR_AUTHORITIES,
@@ -205,15 +216,15 @@ final class EnforcingAdmin {
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);
+ int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
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);
+ return new EnforcingAdmin(packageName, componentName, authorities, userId);
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a787a0b3943b..c684af39a25f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -19,12 +19,16 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PolicyUpdatesReceiver;
import android.content.Context;
+import android.os.Bundle;
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;
@@ -44,8 +48,9 @@ final class PolicyDefinition<V> {
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 = ";";
+ private static final String ATTR_CALLBACK_ARGS_SIZE = "size";
+ private static final String ATTR_CALLBACK_ARGS_KEY = "key";
+ private static final String ATTR_CALLBACK_ARGS_VALUE = "value";
static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
@@ -53,7 +58,7 @@ final class PolicyDefinition<V> {
// 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) ->
+ (Boolean value, Context context, Integer userId, Bundle args) ->
PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
new BooleanPolicySerializer());
@@ -75,9 +80,11 @@ final class PolicyDefinition<V> {
static PolicyDefinition<Integer> PERMISSION_GRANT(
@NonNull String packageName, @NonNull String permission) {
+ Bundle callbackArgs = new Bundle();
+ callbackArgs.putString(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME, packageName);
+ callbackArgs.putString(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME, permission);
return PERMISSION_GRANT_NO_ARGS.setArgs(
- DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission),
- new String[]{packageName, permission});
+ DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission), callbackArgs);
}
static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
@@ -87,13 +94,14 @@ final class PolicyDefinition<V> {
EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_LOCAL_ONLY_POLICY,
- (LockTaskPolicy value, Context context, Integer userId, String[] args) ->
+ (LockTaskPolicy value, Context context, Integer userId, Bundle args) ->
PolicyEnforcerCallbacks.setLockTask(value, context, userId),
new LockTaskPolicy.LockTaskPolicySerializer());
private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
- DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS
+ DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS,
+ DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK
);
@@ -103,19 +111,30 @@ final class PolicyDefinition<V> {
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 QuadFunction<V, Context, Integer, Bundle, Boolean> mPolicyEnforcerCallback;
+ private final Bundle mCallbackArgs;
private final PolicySerializer<V> mPolicySerializer;
- private PolicyDefinition<V> setArgs(String key, String[] callbackArgs) {
+ private PolicyDefinition<V> setArgs(String key, Bundle callbackArgs) {
return new PolicyDefinition<>(key, mPolicyDefinitionKey, mResolutionMechanism,
mPolicyFlags, mPolicyEnforcerCallback, mPolicySerializer, callbackArgs);
}
+ @NonNull
String getPolicyKey() {
return mPolicyKey;
}
+ @NonNull
+ String getPolicyDefinitionKey() {
+ return mPolicyDefinitionKey;
+ }
+
+ @Nullable
+ Bundle getCallbackArgs() {
+ return mCallbackArgs;
+ }
+
/**
* Returns {@code true} if the policy is a global policy by nature and can't be applied locally.
*/
@@ -146,7 +165,7 @@ final class PolicyDefinition<V> {
private PolicyDefinition(
String key,
ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
@@ -159,7 +178,7 @@ final class PolicyDefinition<V> {
String key,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, key, resolutionMechanism, policyFlags, policyEnforcerCallback,
policySerializer, /* callbackArs= */ null);
@@ -174,9 +193,9 @@ final class PolicyDefinition<V> {
String policyDefinitionKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer,
- String[] callbackArgs) {
+ Bundle callbackArgs) {
mPolicyKey = policyKey;
mPolicyDefinitionKey = policyDefinitionKey;
mResolutionMechanism = resolutionMechanism;
@@ -193,24 +212,39 @@ final class PolicyDefinition<V> {
serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mPolicyKey);
serializer.attribute(
/* namespace= */ null, ATTR_POLICY_DEFINITION_KEY, mPolicyDefinitionKey);
+ serializer.attributeInt(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS_SIZE,
+ mCallbackArgs == null ? 0 : mCallbackArgs.size());
if (mCallbackArgs != null) {
- serializer.attribute(/* namespace= */ null, ATTR_CALLBACK_ARGS,
- String.join(ATTR_CALLBACK_ARGS_SEPARATOR, mCallbackArgs));
+ int i = 0;
+ for (String key : mCallbackArgs.keySet()) {
+ serializer.attribute(/* namespace= */ null,
+ ATTR_CALLBACK_ARGS_KEY + i, key);
+ serializer.attribute(/* namespace= */ null,
+ ATTR_CALLBACK_ARGS_VALUE + i, mCallbackArgs.getString(key));
+ i++;
+ }
}
}
- static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser) {
+ static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
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);
+ int size = parser.getAttributeInt(/* namespace= */ null, ATTR_CALLBACK_ARGS_SIZE);
+ Bundle callbackArgs = new Bundle();
+
+ for (int i = 0; i < size; i++) {
+ String key = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS_KEY + i);
+ String value = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS_VALUE + i);
+ callbackArgs.putString(key, value);
+ }
// TODO: can we avoid casting?
- if (callbackArgs == null) {
+ if (callbackArgs.isEmpty()) {
return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey);
} else {
return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey).setArgs(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 74b6f9ea114f..c745b31afd9c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -19,9 +19,11 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PolicyUpdatesReceiver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.UserHandle;
import android.permission.AdminPermissionControlParams;
import android.permission.PermissionControllerManager;
@@ -53,14 +55,17 @@ final class PolicyEnforcerCallbacks {
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
- @NonNull String[] args) {
+ @NonNull Bundle args) {
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
- if (args == null || args.length < 2) {
+ if (args == null
+ || !args.containsKey(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME)
+ || !args.containsKey(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME)) {
throw new IllegalArgumentException("Package name and permission name must be "
- + "provided as arguments");
+ + "provided as arguments.");
}
- String packageName = args[0];
- String permissionName = args[1];
+
+ String packageName = args.getString(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME);
+ String permissionName = args.getString(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME);
Objects.requireNonNull(packageName);
Objects.requireNonNull(permissionName);
Objects.requireNonNull(context);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index d3dee98cf7ba..ffde5f858ce6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -119,10 +119,13 @@ final class PolicyState<V> {
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);
+
+ LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins = new LinkedHashMap<>();
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
@@ -134,12 +137,12 @@ final class PolicyState<V> {
if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
&& parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
admin = EnforcingAdmin.readFromXml(parser);
- adminsPolicy.put(admin, value);
+ policiesSetByAdmins.put(admin, value);
}
} else {
Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
}
}
- return new PolicyState<V>(policyDefinition, adminsPolicy, currentResolvedPolicy);
+ return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
}
}