diff options
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); } } |