| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.devicepolicy; |
| |
| import static android.app.admin.DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY; |
| import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID; |
| import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY; |
| import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY; |
| import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMITATION; |
| import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED; |
| import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED; |
| import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET; |
| import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT; |
| |
| import android.Manifest; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.AppGlobals; |
| import android.app.BroadcastOptions; |
| import android.app.admin.DevicePolicyIdentifiers; |
| import android.app.admin.DevicePolicyManager; |
| import android.app.admin.DevicePolicyState; |
| import android.app.admin.IntentFilterPolicyKey; |
| import android.app.admin.PolicyKey; |
| import android.app.admin.PolicyUpdateReceiver; |
| import android.app.admin.PolicyValue; |
| import android.app.admin.TargetUser; |
| import android.app.admin.UserRestrictionPolicyKey; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.UserInfo; |
| import android.content.pm.UserProperties; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Parcel; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.telephony.TelephonyManager; |
| import android.util.AtomicFile; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.util.Xml; |
| |
| import com.android.internal.util.XmlUtils; |
| import com.android.modules.utils.TypedXmlPullParser; |
| import com.android.modules.utils.TypedXmlSerializer; |
| import com.android.server.devicepolicy.flags.FlagUtils; |
| import com.android.server.utils.Slogf; |
| |
| import libcore.io.IoUtils; |
| |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| 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 |
| * admins on the device. |
| */ |
| final class DevicePolicyEngine { |
| static final String TAG = "DevicePolicyEngine"; |
| |
| // TODO(b/281701062): reference role name from role manager once its exposed. |
| static final String DEVICE_LOCK_CONTROLLER_ROLE = |
| "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER"; |
| |
| private static final String CELLULAR_2G_USER_RESTRICTION_ID = |
| DevicePolicyIdentifiers.getIdentifierForUserRestriction( |
| UserManager.DISALLOW_CELLULAR_2G); |
| |
| private final Context mContext; |
| private final UserManager mUserManager; |
| |
| // TODO(b/256849338): add more granular locks |
| private final Object mLock; |
| |
| /** |
| * Map of <userId, Map<policyKey, policyState>> |
| */ |
| private final SparseArray<Map<PolicyKey, PolicyState<?>>> mLocalPolicies; |
| |
| /** |
| * Map of <policyKey, policyState> |
| */ |
| private final Map<PolicyKey, PolicyState<?>> mGlobalPolicies; |
| |
| /** |
| * Map containing the current set of admins in each user with active policies. |
| */ |
| private final SparseArray<Set<EnforcingAdmin>> mEnforcingAdmins; |
| private final SparseArray<HashMap<EnforcingAdmin, Integer>> mAdminPolicySize; |
| |
| //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit |
| private static final int POLICY_SIZE_LIMIT = 99999; |
| |
| private final DeviceAdminServiceController mDeviceAdminServiceController; |
| |
| DevicePolicyEngine( |
| @NonNull Context context, |
| @NonNull DeviceAdminServiceController deviceAdminServiceController, |
| @NonNull Object lock) { |
| mContext = Objects.requireNonNull(context); |
| mDeviceAdminServiceController = Objects.requireNonNull(deviceAdminServiceController); |
| mLock = Objects.requireNonNull(lock); |
| mUserManager = mContext.getSystemService(UserManager.class); |
| mLocalPolicies = new SparseArray<>(); |
| mGlobalPolicies = new HashMap<>(); |
| mEnforcingAdmins = new SparseArray<>(); |
| mAdminPolicySize = new SparseArray<>(); |
| } |
| |
| /** |
| * Set the policy for the provided {@code policyDefinition} (see {@link PolicyDefinition}) and |
| * {@code enforcingAdmin} to the provided {@code value}. |
| * |
| * <p>If {@code skipEnforcePolicy} is true, it sets the policies in the internal data structure |
| * but doesn't call the enforcing logic. |
| */ |
| <V> void setLocalPolicy( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| @Nullable PolicyValue<V> value, |
| int userId, |
| boolean skipEnforcePolicy) { |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| |
| synchronized (mLock) { |
| PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); |
| if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { |
| if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, |
| policyDefinition, userId)) { |
| return; |
| } |
| } |
| |
| if (policyDefinition.isNonCoexistablePolicy()) { |
| setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState, |
| enforcingAdmin, value, userId, skipEnforcePolicy); |
| return; |
| } |
| |
| boolean hasGlobalPolicies = hasGlobalPolicyLocked(policyDefinition); |
| boolean policyChanged; |
| if (hasGlobalPolicies) { |
| PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); |
| policyChanged = localPolicyState.addPolicy( |
| enforcingAdmin, |
| value, |
| globalPolicyState.getPoliciesSetByAdmins()); |
| } else { |
| policyChanged = localPolicyState.addPolicy(enforcingAdmin, value); |
| } |
| |
| // No need to notify admins as no new policy is actually enforced, we're just filling in |
| // the data structures. |
| if (!skipEnforcePolicy) { |
| if (policyChanged) { |
| onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId); |
| } |
| boolean policyEnforced = Objects.equals( |
| localPolicyState.getCurrentResolvedPolicy(), value); |
| // TODO(b/285532044): remove hack and handle properly |
| if (!policyEnforced |
| && policyDefinition.getPolicyKey().getIdentifier().equals( |
| USER_CONTROL_DISABLED_PACKAGES_POLICY)) { |
| PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value; |
| PolicyValue<Set<String>> parsedResolvedValue = |
| (PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy(); |
| policyEnforced = (parsedResolvedValue != null && parsedValue != null |
| && parsedResolvedValue.getValue().containsAll(parsedValue.getValue())); |
| } |
| sendPolicyResultToAdmin( |
| enforcingAdmin, |
| policyDefinition, |
| // TODO: we're always sending this for now, should properly handle errors. |
| policyEnforced |
| ? RESULT_POLICY_SET : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, |
| userId); |
| } |
| |
| updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); |
| |
| write(); |
| |
| applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId); |
| } |
| } |
| |
| /** |
| * Sets a non-coexistable policy, meaning it doesn't get resolved against other policies set |
| * by other admins, and no callbacks are sent to admins, this is just storing and |
| * enforcing the policy. |
| * |
| * <p>Passing a {@code null} value means the policy set by this admin should be removed. |
| */ |
| private <V> void setNonCoexistableLocalPolicyLocked( |
| PolicyDefinition<V> policyDefinition, |
| PolicyState<V> localPolicyState, |
| EnforcingAdmin enforcingAdmin, |
| @Nullable PolicyValue<V> value, |
| int userId, |
| boolean skipEnforcePolicy) { |
| if (value == null) { |
| localPolicyState.removePolicy(enforcingAdmin); |
| } else { |
| localPolicyState.addPolicy(enforcingAdmin, value); |
| } |
| if (!skipEnforcePolicy) { |
| enforcePolicy(policyDefinition, value, userId); |
| } |
| if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) { |
| removeLocalPolicyStateLocked(policyDefinition, userId); |
| } |
| updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); |
| write(); |
| } |
| |
| // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values |
| |
| /** |
| * Set the policy for the provided {@code policyDefinition} |
| * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. |
| */ |
| <V> void setLocalPolicy( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| @NonNull PolicyValue<V> value, |
| int userId) { |
| setLocalPolicy( |
| policyDefinition, enforcingAdmin, value, userId, /* skipEnforcePolicy= */ false); |
| } |
| |
| // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values |
| |
| /** |
| * Removes any previously set policy for the provided {@code policyDefinition} |
| * (see {@link PolicyDefinition}) and {@code enforcingAdmin}. |
| */ |
| <V> void removeLocalPolicy( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| int userId) { |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| |
| synchronized (mLock) { |
| if (!hasLocalPolicyLocked(policyDefinition, userId)) { |
| return; |
| } |
| PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); |
| |
| if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { |
| decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); |
| } |
| |
| if (policyDefinition.isNonCoexistablePolicy()) { |
| setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState, |
| enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false); |
| return; |
| } |
| |
| boolean policyChanged; |
| if (hasGlobalPolicyLocked(policyDefinition)) { |
| PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); |
| policyChanged = localPolicyState.removePolicy( |
| enforcingAdmin, |
| globalPolicyState.getPoliciesSetByAdmins()); |
| } else { |
| policyChanged = localPolicyState.removePolicy(enforcingAdmin); |
| } |
| |
| if (policyChanged) { |
| onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId); |
| } |
| |
| // For a removePolicy to be enforced, it means no current policy exists |
| sendPolicyResultToAdmin( |
| enforcingAdmin, |
| policyDefinition, |
| // TODO: we're always sending this for now, should properly handle errors. |
| RESULT_POLICY_CLEARED, |
| userId); |
| |
| if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) { |
| removeLocalPolicyStateLocked(policyDefinition, userId); |
| } |
| |
| updateDeviceAdminServiceOnPolicyRemoveLocked(enforcingAdmin); |
| |
| write(); |
| |
| applyToInheritableProfiles(policyDefinition, enforcingAdmin, /*value */ null, userId); |
| } |
| } |
| |
| /** |
| * If any of child user has property {@link UserProperties#INHERIT_DEVICE_POLICY_FROM_PARENT} |
| * set then propagate the policy to it if value is not null |
| * else remove the policy from child. |
| */ |
| private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition, |
| EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) { |
| if (policyDefinition.isInheritable()) { |
| Binder.withCleanCallingIdentity(() -> { |
| List<UserInfo> userInfos = mUserManager.getProfiles(userId); |
| for (UserInfo childUserInfo : userInfos) { |
| int childUserId = childUserInfo.getUserHandle().getIdentifier(); |
| if (isProfileOfUser(childUserId, userId) |
| && isInheritDevicePolicyFromParent(childUserInfo)) { |
| if (value != null) { |
| setLocalPolicy(policyDefinition, enforcingAdmin, value, childUserId); |
| } else { |
| removeLocalPolicy(policyDefinition, enforcingAdmin, childUserId); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Checks if given parentUserId is direct parent of childUserId. |
| */ |
| private boolean isProfileOfUser(int childUserId, int parentUserId) { |
| UserInfo parentInfo = mUserManager.getProfileParent(childUserId); |
| return childUserId != parentUserId && parentInfo != null |
| && parentInfo.getUserHandle().getIdentifier() == parentUserId; |
| } |
| |
| private boolean isInheritDevicePolicyFromParent(UserInfo userInfo) { |
| UserProperties userProperties = mUserManager.getUserProperties(userInfo.getUserHandle()); |
| return userProperties != null && mUserManager.getUserProperties(userInfo.getUserHandle()) |
| .getInheritDevicePolicy() == INHERIT_DEVICE_POLICY_FROM_PARENT; |
| } |
| |
| /** |
| * Enforces the new policy and notifies relevant admins. |
| */ |
| private <V> void onLocalPolicyChangedLocked( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| int userId) { |
| |
| PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); |
| enforcePolicy( |
| policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId); |
| |
| // Send policy updates to admins who've set it locally |
| sendPolicyChangedToAdminsLocked( |
| localPolicyState, |
| enforcingAdmin, |
| policyDefinition, |
| // This policy change is only relevant to a single user, not the global |
| // policy value, |
| userId); |
| |
| // Send policy updates to admins who've set it globally |
| if (hasGlobalPolicyLocked(policyDefinition)) { |
| PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); |
| sendPolicyChangedToAdminsLocked( |
| globalPolicyState, |
| enforcingAdmin, |
| policyDefinition, |
| userId); |
| } |
| sendDevicePolicyChangedToSystem(userId); |
| } |
| |
| /** |
| * Set the policy for the provided {@code policyDefinition} |
| * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. |
| */ |
| <V> void setGlobalPolicy( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| @NonNull PolicyValue<V> value) { |
| setGlobalPolicy(policyDefinition, enforcingAdmin, value, /* skipEnforcePolicy= */ false); |
| } |
| |
| // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values |
| |
| /** |
| * Set the policy for the provided {@code policyDefinition} |
| * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. |
| */ |
| <V> void setGlobalPolicy( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| @NonNull PolicyValue<V> value, |
| boolean skipEnforcePolicy) { |
| |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| Objects.requireNonNull(value); |
| |
| synchronized (mLock) { |
| PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); |
| if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { |
| if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, |
| policyDefinition, UserHandle.USER_ALL)) { |
| return; |
| } |
| } |
| // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code |
| // that honors the restriction once there's an API available |
| if (checkFor2gFailure(policyDefinition, enforcingAdmin)) { |
| Log.i(TAG, |
| "Device does not support capabilities required to disable 2g. Not setting" |
| + " global policy state."); |
| return; |
| } |
| |
| boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value); |
| boolean policyAppliedOnAllUsers = applyGlobalPolicyOnUsersWithLocalPoliciesLocked( |
| policyDefinition, enforcingAdmin, value, skipEnforcePolicy); |
| |
| // No need to notify admins as no new policy is actually enforced, we're just filling in |
| // the data structures. |
| if (!skipEnforcePolicy) { |
| if (policyChanged) { |
| onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin); |
| } |
| |
| boolean policyAppliedGlobally = Objects.equals( |
| globalPolicyState.getCurrentResolvedPolicy(), value); |
| // TODO(b/285532044): remove hack and handle properly |
| if (!policyAppliedGlobally |
| && policyDefinition.getPolicyKey().getIdentifier().equals( |
| USER_CONTROL_DISABLED_PACKAGES_POLICY)) { |
| PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value; |
| PolicyValue<Set<String>> parsedResolvedValue = |
| (PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy(); |
| policyAppliedGlobally = (parsedResolvedValue != null && parsedValue != null |
| && parsedResolvedValue.getValue().containsAll(parsedValue.getValue())); |
| } |
| |
| boolean policyApplied = policyAppliedGlobally && policyAppliedOnAllUsers; |
| |
| sendPolicyResultToAdmin( |
| enforcingAdmin, |
| policyDefinition, |
| // TODO: we're always sending this for now, should properly handle errors. |
| policyApplied ? RESULT_POLICY_SET : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, |
| UserHandle.USER_ALL); |
| } |
| |
| updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); |
| |
| write(); |
| } |
| } |
| |
| // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values |
| |
| /** |
| * Removes any previously set policy for the provided {@code policyDefinition} |
| * (see {@link PolicyDefinition}) and {@code enforcingAdmin}. |
| */ |
| <V> void removeGlobalPolicy( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin) { |
| |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| |
| synchronized (mLock) { |
| PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); |
| |
| if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { |
| decreasePolicySizeForAdmin(policyState, enforcingAdmin); |
| } |
| |
| boolean policyChanged = policyState.removePolicy(enforcingAdmin); |
| |
| if (policyChanged) { |
| onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin); |
| } |
| |
| applyGlobalPolicyOnUsersWithLocalPoliciesLocked(policyDefinition, enforcingAdmin, |
| /* value= */ null, /* skipEnforcePolicy= */ false); |
| |
| sendPolicyResultToAdmin( |
| enforcingAdmin, |
| policyDefinition, |
| // TODO: we're always sending this for now, should properly handle errors. |
| RESULT_POLICY_CLEARED, |
| UserHandle.USER_ALL); |
| |
| if (policyState.getPoliciesSetByAdmins().isEmpty()) { |
| removeGlobalPolicyStateLocked(policyDefinition); |
| } |
| |
| updateDeviceAdminServiceOnPolicyRemoveLocked(enforcingAdmin); |
| |
| write(); |
| } |
| } |
| |
| /** |
| * Enforces the new policy globally and notifies relevant admins. |
| */ |
| private <V> void onGlobalPolicyChangedLocked( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin) { |
| PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); |
| |
| enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), |
| UserHandle.USER_ALL); |
| |
| sendPolicyChangedToAdminsLocked( |
| policyState, |
| enforcingAdmin, |
| policyDefinition, |
| UserHandle.USER_ALL); |
| |
| sendDevicePolicyChangedToSystem(UserHandle.USER_ALL); |
| } |
| |
| /** |
| * Tries to enforce the global policy locally on all users that have the same policy set |
| * locally, this is only applicable to policies that can be set locally or globally |
| * (e.g. setCameraDisabled, setScreenCaptureDisabled) rather than |
| * policies that are global by nature (e.g. setting Wifi enabled/disabled). |
| * |
| * <p> A {@code null} policy value means the policy was removed |
| * |
| * <p>Returns {@code true} if the policy is enforced successfully on all users. |
| */ |
| private <V> boolean applyGlobalPolicyOnUsersWithLocalPoliciesLocked( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| @Nullable PolicyValue<V> value, |
| boolean skipEnforcePolicy) { |
| // Global only policies can't be applied locally, return early. |
| if (policyDefinition.isGlobalOnlyPolicy()) { |
| return true; |
| } |
| boolean isAdminPolicyApplied = true; |
| for (int i = 0; i < mLocalPolicies.size(); i++) { |
| int userId = mLocalPolicies.keyAt(i); |
| if (!hasLocalPolicyLocked(policyDefinition, userId)) { |
| continue; |
| } |
| |
| PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); |
| PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); |
| |
| boolean policyChanged = localPolicyState.resolvePolicy( |
| globalPolicyState.getPoliciesSetByAdmins()); |
| if (policyChanged && !skipEnforcePolicy) { |
| enforcePolicy( |
| policyDefinition, |
| localPolicyState.getCurrentResolvedPolicy(), |
| userId); |
| sendPolicyChangedToAdminsLocked( |
| localPolicyState, |
| enforcingAdmin, |
| policyDefinition, |
| // Even though this is caused by a global policy change, admins who've set |
| // it locally should only care about the local user state. |
| userId); |
| |
| } |
| // TODO(b/285532044): remove hack and handle properly |
| if (policyDefinition.getPolicyKey().getIdentifier().equals( |
| USER_CONTROL_DISABLED_PACKAGES_POLICY)) { |
| if (!Objects.equals(value, localPolicyState.getCurrentResolvedPolicy())) { |
| PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value; |
| PolicyValue<Set<String>> parsedResolvedValue = |
| (PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy(); |
| isAdminPolicyApplied &= (parsedResolvedValue != null && parsedValue != null |
| && parsedResolvedValue.getValue().containsAll(parsedValue.getValue())); |
| } |
| } else { |
| isAdminPolicyApplied &= Objects.equals( |
| value, localPolicyState.getCurrentResolvedPolicy()); |
| } |
| } |
| return isAdminPolicyApplied; |
| } |
| |
| /** |
| * Retrieves the resolved policy for the provided {@code policyDefinition} and {@code userId}. |
| */ |
| @Nullable |
| <V> V getResolvedPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) { |
| Objects.requireNonNull(policyDefinition); |
| |
| synchronized (mLock) { |
| PolicyValue<V> resolvedValue = null; |
| if (hasLocalPolicyLocked(policyDefinition, userId)) { |
| resolvedValue = getLocalPolicyStateLocked( |
| policyDefinition, userId).getCurrentResolvedPolicy(); |
| } else if (hasGlobalPolicyLocked(policyDefinition)) { |
| resolvedValue = getGlobalPolicyStateLocked( |
| policyDefinition).getCurrentResolvedPolicy(); |
| } |
| return resolvedValue == null ? null : resolvedValue.getValue(); |
| } |
| } |
| |
| /** |
| * Retrieves the policy set by the admin for the provided {@code policyDefinition} and |
| * {@code userId} if one was set, otherwise returns {@code null}. |
| */ |
| @Nullable |
| <V> V getLocalPolicySetByAdmin( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| int userId) { |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| |
| synchronized (mLock) { |
| if (!hasLocalPolicyLocked(policyDefinition, userId)) { |
| return null; |
| } |
| PolicyValue<V> value = getLocalPolicyStateLocked(policyDefinition, userId) |
| .getPoliciesSetByAdmins().get(enforcingAdmin); |
| return value == null ? null : value.getValue(); |
| } |
| } |
| |
| /** |
| * Retrieves the global policy set by the admin for the provided {@code policyDefinition} |
| * if one was set, otherwise returns {@code null}. |
| */ |
| @Nullable |
| <V> V getGlobalPolicySetByAdmin( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin) { |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| |
| synchronized (mLock) { |
| if (!hasGlobalPolicyLocked(policyDefinition)) { |
| return null; |
| } |
| PolicyValue<V> value = getGlobalPolicyStateLocked(policyDefinition) |
| .getPoliciesSetByAdmins().get(enforcingAdmin); |
| return value == null ? null : value.getValue(); |
| } |
| } |
| |
| /** |
| * Retrieves the values set for the provided {@code policyDefinition} by each admin. |
| */ |
| @NonNull |
| <V> LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getLocalPoliciesSetByAdmins( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| int userId) { |
| Objects.requireNonNull(policyDefinition); |
| |
| synchronized (mLock) { |
| if (!hasLocalPolicyLocked(policyDefinition, userId)) { |
| return new LinkedHashMap<>(); |
| } |
| return getLocalPolicyStateLocked(policyDefinition, userId).getPoliciesSetByAdmins(); |
| } |
| } |
| |
| /** |
| * Retrieves the values set for the provided {@code policyDefinition} by each admin. |
| */ |
| @NonNull |
| <V> LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getGlobalPoliciesSetByAdmins( |
| @NonNull PolicyDefinition<V> policyDefinition) { |
| Objects.requireNonNull(policyDefinition); |
| |
| synchronized (mLock) { |
| if (!hasGlobalPolicyLocked(policyDefinition)) { |
| return new LinkedHashMap<>(); |
| } |
| return getGlobalPolicyStateLocked(policyDefinition).getPoliciesSetByAdmins(); |
| } |
| } |
| |
| /** |
| * Returns the policies set by the given admin that share the same |
| * {@link PolicyKey#getIdentifier()} as the provided {@code policyDefinition}. |
| * |
| * <p>For example, getLocalPolicyKeysSetByAdmin(PERMISSION_GRANT, admin) returns all permission |
| * grants set by the given admin. |
| * |
| * <p>Note that this will always return at most one item for policies that do not require |
| * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs |
| * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}). |
| */ |
| @NonNull |
| <V> Set<PolicyKey> getLocalPolicyKeysSetByAdmin( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin, |
| int userId) { |
| Objects.requireNonNull(policyDefinition); |
| Objects.requireNonNull(enforcingAdmin); |
| |
| synchronized (mLock) { |
| if (policyDefinition.isGlobalOnlyPolicy() || !mLocalPolicies.contains(userId)) { |
| return Set.of(); |
| } |
| Set<PolicyKey> keys = new HashSet<>(); |
| for (PolicyKey key : mLocalPolicies.get(userId).keySet()) { |
| if (key.hasSameIdentifierAs(policyDefinition.getPolicyKey()) |
| && mLocalPolicies.get(userId).get(key).getPoliciesSetByAdmins() |
| .containsKey(enforcingAdmin)) { |
| keys.add(key); |
| } |
| } |
| return keys; |
| } |
| } |
| |
| /** |
| * Returns all the {@code policyKeys} set by any admin that share the same |
| * {@link PolicyKey#getIdentifier()} as the provided {@code policyDefinition}. |
| * |
| * <p>For example, getLocalPolicyKeysSetByAllAdmins(PERMISSION_GRANT) returns all permission |
| * grants set by any admin. |
| * |
| * <p>Note that this will always return at most one item for policies that do not require |
| * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs |
| * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}). |
| */ |
| @NonNull |
| <V> Set<PolicyKey> getLocalPolicyKeysSetByAllAdmins( |
| @NonNull PolicyDefinition<V> policyDefinition, |
| int userId) { |
| Objects.requireNonNull(policyDefinition); |
| |
| synchronized (mLock) { |
| if (policyDefinition.isGlobalOnlyPolicy() || !mLocalPolicies.contains(userId)) { |
| return Set.of(); |
| } |
| Set<PolicyKey> keys = new HashSet<>(); |
| for (PolicyKey key : mLocalPolicies.get(userId).keySet()) { |
| if (key.hasSameIdentifierAs(policyDefinition.getPolicyKey())) { |
| keys.add(key); |
| } |
| } |
| return keys; |
| } |
| } |
| |
| /** |
| * Returns all user restriction policies set by the given admin. |
| * |
| * <p>Pass in {@link UserHandle#USER_ALL} for {@code userId} to get global restrictions set by |
| * the admin |
| */ |
| @NonNull |
| Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdmin( |
| @NonNull EnforcingAdmin admin, |
| int userId) { |
| Objects.requireNonNull(admin); |
| synchronized (mLock) { |
| if (userId == UserHandle.USER_ALL) { |
| return getUserRestrictionPolicyKeysForAdminLocked(mGlobalPolicies, admin); |
| } |
| if (!mLocalPolicies.contains(userId)) { |
| return Set.of(); |
| } |
| return getUserRestrictionPolicyKeysForAdminLocked(mLocalPolicies.get(userId), admin); |
| } |
| } |
| |
| <V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) { |
| synchronized (mLock) { |
| Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet()); |
| for (PolicyKey policy : globalPolicies) { |
| PolicyState<?> policyState = mGlobalPolicies.get(policy); |
| if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) { |
| PolicyDefinition<V> policyDefinition = |
| (PolicyDefinition<V>) policyState.getPolicyDefinition(); |
| PolicyValue<V> policyValue = |
| (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin); |
| setGlobalPolicy(policyDefinition, newAdmin, policyValue); |
| } |
| } |
| |
| for (int i = 0; i < mLocalPolicies.size(); i++) { |
| int userId = mLocalPolicies.keyAt(i); |
| Set<PolicyKey> localPolicies = new HashSet<>( |
| mLocalPolicies.get(userId).keySet()); |
| for (PolicyKey policy : localPolicies) { |
| PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy); |
| if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) { |
| PolicyDefinition<V> policyDefinition = |
| (PolicyDefinition<V>) policyState.getPolicyDefinition(); |
| PolicyValue<V> policyValue = |
| (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin); |
| setLocalPolicy(policyDefinition, newAdmin, policyValue, userId); |
| } |
| } |
| } |
| } |
| removePoliciesForAdmin(oldAdmin); |
| } |
| |
| private Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdminLocked( |
| Map<PolicyKey, PolicyState<?>> policies, |
| EnforcingAdmin admin) { |
| Set<UserRestrictionPolicyKey> keys = new HashSet<>(); |
| for (PolicyKey key : policies.keySet()) { |
| if (!policies.get(key).getPolicyDefinition().isUserRestrictionPolicy()) { |
| continue; |
| } |
| // User restriction policies are always boolean |
| PolicyValue<Boolean> value = (PolicyValue<Boolean>) policies.get(key) |
| .getPoliciesSetByAdmins().get(admin); |
| if (value == null || !value.getValue()) { |
| continue; |
| } |
| keys.add((UserRestrictionPolicyKey) key); |
| } |
| return keys; |
| } |
| |
| private <V> boolean hasLocalPolicyLocked(PolicyDefinition<V> policyDefinition, int userId) { |
| if (policyDefinition.isGlobalOnlyPolicy()) { |
| return false; |
| } |
| if (!mLocalPolicies.contains(userId)) { |
| return false; |
| } |
| if (!mLocalPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) { |
| return false; |
| } |
| return !mLocalPolicies.get(userId).get(policyDefinition.getPolicyKey()) |
| .getPoliciesSetByAdmins().isEmpty(); |
| } |
| |
| private <V> boolean hasGlobalPolicyLocked(PolicyDefinition<V> policyDefinition) { |
| if (policyDefinition.isLocalOnlyPolicy()) { |
| return false; |
| } |
| if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) { |
| return false; |
| } |
| return !mGlobalPolicies.get(policyDefinition.getPolicyKey()).getPoliciesSetByAdmins() |
| .isEmpty(); |
| } |
| |
| @NonNull |
| private <V> PolicyState<V> getLocalPolicyStateLocked( |
| PolicyDefinition<V> policyDefinition, int userId) { |
| |
| if (policyDefinition.isGlobalOnlyPolicy()) { |
| throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only" |
| + " policy."); |
| } |
| |
| if (!mLocalPolicies.contains(userId)) { |
| mLocalPolicies.put(userId, new HashMap<>()); |
| } |
| if (!mLocalPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) { |
| mLocalPolicies.get(userId).put( |
| policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition)); |
| } |
| return getPolicyStateLocked(mLocalPolicies.get(userId), policyDefinition); |
| } |
| |
| private <V> void removeLocalPolicyStateLocked( |
| PolicyDefinition<V> policyDefinition, int userId) { |
| if (!mLocalPolicies.contains(userId)) { |
| return; |
| } |
| mLocalPolicies.get(userId).remove(policyDefinition.getPolicyKey()); |
| } |
| |
| @NonNull |
| private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) { |
| if (policyDefinition.isLocalOnlyPolicy()) { |
| throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only" |
| + " policy."); |
| } |
| |
| if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) { |
| mGlobalPolicies.put( |
| policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition)); |
| } |
| return getPolicyStateLocked(mGlobalPolicies, policyDefinition); |
| } |
| |
| private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) { |
| mGlobalPolicies.remove(policyDefinition.getPolicyKey()); |
| } |
| |
| private static <V> PolicyState<V> getPolicyStateLocked( |
| Map<PolicyKey, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) { |
| try { |
| // This will not throw an exception because policyDefinition is of type V, so unless |
| // we've created two policies with the same key but different types - we can only have |
| // stored a PolicyState of the right type. |
| PolicyState<V> policyState = (PolicyState<V>) policies.get( |
| policyDefinition.getPolicyKey()); |
| return policyState; |
| } catch (ClassCastException exception) { |
| // TODO: handle exception properly |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition, |
| @Nullable PolicyValue<V> policyValue, int userId) { |
| // null policyValue means remove any enforced policies, ensure callbacks handle this |
| // properly |
| policyDefinition.enforcePolicy( |
| policyValue == null ? null : policyValue.getValue(), mContext, userId); |
| } |
| |
| private void sendDevicePolicyChangedToSystem(int userId) { |
| Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); |
| intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| Bundle options = new BroadcastOptions() |
| .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) |
| .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) |
| .toBundle(); |
| Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser( |
| intent, |
| new UserHandle(userId), |
| /* receiverPermissions= */ null, |
| options)); |
| } |
| |
| private <V> void sendPolicyResultToAdmin( |
| EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) { |
| Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT); |
| intent.setPackage(admin.getPackageName()); |
| |
| Binder.withCleanCallingIdentity(() -> { |
| 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(); |
| policyDefinition.getPolicyKey().writeToBundle(extras); |
| extras.putInt( |
| EXTRA_POLICY_TARGET_USER_ID, |
| getTargetUser(admin.getUserId(), userId)); |
| extras.putInt( |
| EXTRA_POLICY_UPDATE_RESULT_KEY, |
| result); |
| |
| 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 sendPolicyChangedToAdminsLocked( |
| PolicyState<V> policyState, |
| EnforcingAdmin callingAdmin, |
| PolicyDefinition<V> policyDefinition, |
| int userId) { |
| for (EnforcingAdmin admin : policyState.getPoliciesSetByAdmins().keySet()) { |
| // We're sending a separate broadcast for the calling admin with the result. |
| if (admin.equals(callingAdmin)) { |
| continue; |
| } |
| int result = Objects.equals( |
| policyState.getPoliciesSetByAdmins().get(admin), |
| policyState.getCurrentResolvedPolicy()) |
| ? RESULT_POLICY_SET : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY; |
| maybeSendOnPolicyChanged( |
| admin, policyDefinition, result, userId); |
| } |
| } |
| |
| private <V> void maybeSendOnPolicyChanged( |
| EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int reason, |
| int userId) { |
| Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_CHANGED); |
| intent.setPackage(admin.getPackageName()); |
| |
| Binder.withCleanCallingIdentity(() -> { |
| 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(); |
| policyDefinition.getPolicyKey().writeToBundle(extras); |
| extras.putInt( |
| EXTRA_POLICY_TARGET_USER_ID, |
| getTargetUser(admin.getUserId(), userId)); |
| extras.putInt(EXTRA_POLICY_UPDATE_RESULT_KEY, reason); |
| intent.putExtras(extras); |
| intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| |
| 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 int getTargetUser(int adminUserId, int targetUserId) { |
| if (targetUserId == UserHandle.USER_ALL) { |
| return TargetUser.GLOBAL_USER_ID; |
| } |
| if (adminUserId == targetUserId) { |
| return TargetUser.LOCAL_USER_ID; |
| } |
| if (getProfileParentId(adminUserId) == targetUserId) { |
| return TargetUser.PARENT_USER_ID; |
| } |
| return TargetUser.UNKNOWN_USER_ID; |
| } |
| |
| private int getProfileParentId(int userId) { |
| return Binder.withCleanCallingIdentity(() -> { |
| UserInfo parentUser = mUserManager.getProfileParent(userId); |
| return parentUser != null ? parentUser.id : userId; |
| }); |
| } |
| |
| /** |
| * Starts/Stops the services that handle {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} |
| * in the enforcing admins for the given {@code userId}. |
| */ |
| private void updateDeviceAdminsServicesForUser( |
| int userId, boolean enable, @NonNull String actionForLog) { |
| if (!enable) { |
| mDeviceAdminServiceController.stopServicesForUser( |
| userId, actionForLog); |
| } else { |
| for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) { |
| // DPCs are handled separately in DPMS, no need to reestablish the connection here. |
| if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { |
| continue; |
| } |
| mDeviceAdminServiceController.startServiceForAdmin( |
| admin.getPackageName(), userId, actionForLog); |
| } |
| } |
| } |
| |
| /** |
| * Handles internal state related to a user getting started. |
| */ |
| void handleStartUser(int userId) { |
| updateDeviceAdminsServicesForUser( |
| userId, /* enable= */ true, /* actionForLog= */ "start-user"); |
| } |
| |
| /** |
| * Handles internal state related to a user getting started. |
| */ |
| void handleUnlockUser(int userId) { |
| updateDeviceAdminsServicesForUser( |
| userId, /* enable= */ true, /* actionForLog= */ "unlock-user"); |
| } |
| |
| /** |
| * Handles internal state related to a user getting stopped. |
| */ |
| void handleStopUser(int userId) { |
| updateDeviceAdminsServicesForUser( |
| userId, /* enable= */ false, /* actionForLog= */ "stop-user"); |
| } |
| |
| /** |
| * Handles internal state related to packages getting updated. |
| */ |
| void handlePackageChanged( |
| @Nullable String updatedPackage, int userId, @Nullable String removedDpcPackage) { |
| Binder.withCleanCallingIdentity(() -> { |
| Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId); |
| if (removedDpcPackage != null) { |
| for (EnforcingAdmin admin : admins) { |
| if (removedDpcPackage.equals(admin.getPackageName())) { |
| removePoliciesForAdmin(admin); |
| return; |
| } |
| } |
| } |
| for (EnforcingAdmin admin : admins) { |
| if (updatedPackage == null || updatedPackage.equals(admin.getPackageName())) { |
| if (!isPackageInstalled(admin.getPackageName(), userId)) { |
| Slogf.i(TAG, String.format( |
| "Admin package %s not found for user %d, removing admin policies", |
| admin.getPackageName(), userId)); |
| // remove policies for the uninstalled package |
| removePoliciesForAdmin(admin); |
| return; |
| } |
| } |
| } |
| if (updatedPackage != null) { |
| updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId); |
| removePersistentPreferredActivityPoliciesForPackage(updatedPackage, userId); |
| } |
| }); |
| } |
| |
| private void removePersistentPreferredActivityPoliciesForPackage( |
| @NonNull String packageName, int userId) { |
| Set<PolicyKey> policyKeys = getLocalPolicyKeysSetByAllAdmins( |
| PolicyDefinition.GENERIC_PERSISTENT_PREFERRED_ACTIVITY, userId); |
| for (PolicyKey key : policyKeys) { |
| if (!(key instanceof IntentFilterPolicyKey)) { |
| throw new IllegalStateException("PolicyKey for " |
| + "PERSISTENT_PREFERRED_ACTIVITY is not of type " |
| + "IntentFilterPolicyKey"); |
| } |
| IntentFilterPolicyKey parsedKey = |
| (IntentFilterPolicyKey) key; |
| IntentFilter intentFilter = Objects.requireNonNull(parsedKey.getIntentFilter()); |
| PolicyDefinition<ComponentName> policyDefinition = |
| PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(intentFilter); |
| LinkedHashMap<EnforcingAdmin, PolicyValue<ComponentName>> policies = |
| getLocalPoliciesSetByAdmins( |
| policyDefinition, |
| userId); |
| IPackageManager packageManager = AppGlobals.getPackageManager(); |
| for (EnforcingAdmin admin : policies.keySet()) { |
| if (policies.get(admin).getValue() != null |
| && policies.get(admin).getValue().getPackageName().equals(packageName)) { |
| try { |
| if (packageManager.getPackageInfo(packageName, 0, userId) == null |
| || packageManager.getActivityInfo( |
| policies.get(admin).getValue(), 0, userId) == null) { |
| Slogf.e(TAG, String.format( |
| "Persistent preferred activity in package %s not found for " |
| + "user %d, removing policy for admin", |
| packageName, userId)); |
| removeLocalPolicy(policyDefinition, admin, userId); |
| } |
| } catch (RemoteException re) { |
| // Shouldn't happen. |
| Slogf.wtf(TAG, "Error handling package changes", re); |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean isPackageInstalled(String packageName, int userId) { |
| try { |
| return AppGlobals.getPackageManager().getPackageInfo( |
| packageName, 0, userId) != null; |
| } catch (RemoteException re) { |
| // Shouldn't happen. |
| Slogf.wtf(TAG, "Error handling package changes", re); |
| return true; |
| } |
| } |
| |
| /** |
| * Handles internal state related to a user getting removed. |
| */ |
| void handleUserRemoved(int userId) { |
| removeLocalPoliciesForUser(userId); |
| removePoliciesForAdminsOnUser(userId); |
| } |
| |
| /** |
| * Handles internal state related to a user getting created. |
| */ |
| void handleUserCreated(UserInfo user) { |
| enforcePoliciesOnInheritableProfilesIfApplicable(user); |
| } |
| |
| /** |
| * Handles internal state related to roles getting updated. |
| */ |
| void handleRoleChanged(@NonNull String roleName, int userId) { |
| // TODO(b/256852787): handle all roles changing. |
| if (!DEVICE_LOCK_CONTROLLER_ROLE.equals(roleName)) { |
| // We only support device lock controller role for now. |
| return; |
| } |
| String roleAuthority = EnforcingAdmin.getRoleAuthorityOf(roleName); |
| Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId); |
| for (EnforcingAdmin admin : admins) { |
| if (admin.hasAuthority(roleAuthority)) { |
| admin.reloadRoleAuthorities(); |
| // remove admin policies if role was lost |
| if (!admin.hasAuthority(roleAuthority)) { |
| removePoliciesForAdmin(admin); |
| } |
| } |
| } |
| } |
| |
| private void enforcePoliciesOnInheritableProfilesIfApplicable(UserInfo user) { |
| if (!user.isProfile()) { |
| return; |
| } |
| |
| Binder.withCleanCallingIdentity(() -> { |
| UserProperties userProperties = mUserManager.getUserProperties(user.getUserHandle()); |
| if (userProperties == null || userProperties.getInheritDevicePolicy() |
| != INHERIT_DEVICE_POLICY_FROM_PARENT) { |
| return; |
| } |
| |
| int userId = user.id; |
| // Apply local policies present on parent to newly created child profile. |
| UserInfo parentInfo = mUserManager.getProfileParent(userId); |
| if (parentInfo == null || parentInfo.getUserHandle().getIdentifier() == userId) { |
| return; |
| } |
| synchronized (mLock) { |
| if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) { |
| return; |
| } |
| for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get( |
| parentInfo.getUserHandle().getIdentifier()).entrySet()) { |
| enforcePolicyOnUserLocked(userId, entry.getValue()); |
| } |
| } |
| }); |
| } |
| |
| private <V> void enforcePolicyOnUserLocked(int userId, PolicyState<V> policyState) { |
| if (!policyState.getPolicyDefinition().isInheritable()) { |
| return; |
| } |
| for (Map.Entry<EnforcingAdmin, PolicyValue<V>> enforcingAdminEntry : |
| policyState.getPoliciesSetByAdmins().entrySet()) { |
| setLocalPolicy(policyState.getPolicyDefinition(), |
| enforcingAdminEntry.getKey(), |
| enforcingAdminEntry.getValue(), |
| userId); |
| } |
| } |
| |
| /** |
| * Returns all current enforced policies set on the device, and the individual values set by |
| * each admin. Global policies are returned under {@link UserHandle#ALL}. |
| */ |
| @NonNull |
| DevicePolicyState getDevicePolicyState() { |
| synchronized (mLock) { |
| Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies = |
| new HashMap<>(); |
| for (int i = 0; i < mLocalPolicies.size(); i++) { |
| UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i)); |
| policies.put(user, new HashMap<>()); |
| for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) { |
| policies.get(user).put( |
| policyKey, |
| mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState()); |
| } |
| } |
| if (!mGlobalPolicies.isEmpty()) { |
| policies.put(UserHandle.ALL, new HashMap<>()); |
| for (PolicyKey policyKey : mGlobalPolicies.keySet()) { |
| policies.get(UserHandle.ALL).put( |
| policyKey, |
| mGlobalPolicies.get(policyKey).getParcelablePolicyState()); |
| } |
| } |
| return new DevicePolicyState(policies); |
| } |
| } |
| |
| |
| /** |
| * Removes all local and global policies set by that admin. |
| */ |
| void removePoliciesForAdmin(EnforcingAdmin admin) { |
| synchronized (mLock) { |
| Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet()); |
| for (PolicyKey policy : globalPolicies) { |
| PolicyState<?> policyState = mGlobalPolicies.get(policy); |
| if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { |
| removeGlobalPolicy(policyState.getPolicyDefinition(), admin); |
| } |
| } |
| |
| for (int i = 0; i < mLocalPolicies.size(); i++) { |
| Set<PolicyKey> localPolicies = new HashSet<>( |
| mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()); |
| for (PolicyKey policy : localPolicies) { |
| PolicyState<?> policyState = mLocalPolicies.get( |
| mLocalPolicies.keyAt(i)).get(policy); |
| if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { |
| removeLocalPolicy( |
| policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i)); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes all local policies for the provided {@code userId}. |
| */ |
| private void removeLocalPoliciesForUser(int userId) { |
| synchronized (mLock) { |
| if (!mLocalPolicies.contains(userId)) { |
| // No policies on user |
| return; |
| } |
| |
| Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet()); |
| for (PolicyKey policy : localPolicies) { |
| PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy); |
| Set<EnforcingAdmin> admins = new HashSet<>( |
| policyState.getPoliciesSetByAdmins().keySet()); |
| for (EnforcingAdmin admin : admins) { |
| removeLocalPolicy( |
| policyState.getPolicyDefinition(), admin, userId); |
| } |
| } |
| |
| mLocalPolicies.remove(userId); |
| } |
| } |
| |
| /** |
| * Removes all local and global policies for admins installed in the provided |
| * {@code userId}. |
| */ |
| private void removePoliciesForAdminsOnUser(int userId) { |
| Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId); |
| |
| for (EnforcingAdmin admin : admins) { |
| removePoliciesForAdmin(admin); |
| } |
| } |
| |
| /** |
| * Reestablishes the service that handles |
| * {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package |
| * was updated, as a package update results in the persistent connection getting reset. |
| */ |
| private void updateDeviceAdminServiceOnPackageChanged( |
| @NonNull String updatedPackage, int userId) { |
| for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) { |
| // DPCs are handled separately in DPMS, no need to reestablish the connection here. |
| if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { |
| continue; |
| } |
| if (updatedPackage.equals(admin.getPackageName())) { |
| mDeviceAdminServiceController.startServiceForAdmin( |
| updatedPackage, userId, /* actionForLog= */ "package-broadcast"); |
| } |
| } |
| } |
| |
| /** |
| * Called after an admin policy has been added to start binding to the admin if a connection |
| * was not already established. |
| */ |
| private void updateDeviceAdminServiceOnPolicyAddLocked(@NonNull EnforcingAdmin enforcingAdmin) { |
| int userId = enforcingAdmin.getUserId(); |
| |
| if (mEnforcingAdmins.contains(userId) |
| && mEnforcingAdmins.get(userId).contains(enforcingAdmin)) { |
| return; |
| } |
| |
| if (!mEnforcingAdmins.contains(enforcingAdmin.getUserId())) { |
| mEnforcingAdmins.put(enforcingAdmin.getUserId(), new HashSet<>()); |
| } |
| mEnforcingAdmins.get(enforcingAdmin.getUserId()).add(enforcingAdmin); |
| |
| // A connection is established with DPCs as soon as they are provisioned, so no need to |
| // connect when a policy is set. |
| if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { |
| return; |
| } |
| mDeviceAdminServiceController.startServiceForAdmin( |
| enforcingAdmin.getPackageName(), |
| userId, |
| /* actionForLog= */ "policy-added"); |
| } |
| |
| /** |
| * Called after an admin policy has been removed to stop binding to the admin if they no longer |
| * have any policies set. |
| */ |
| private void updateDeviceAdminServiceOnPolicyRemoveLocked( |
| @NonNull EnforcingAdmin enforcingAdmin) { |
| if (doesAdminHavePoliciesLocked(enforcingAdmin)) { |
| return; |
| } |
| int userId = enforcingAdmin.getUserId(); |
| if (mEnforcingAdmins.contains(userId)) { |
| mEnforcingAdmins.get(userId).remove(enforcingAdmin); |
| if (mEnforcingAdmins.get(userId).isEmpty()) { |
| mEnforcingAdmins.remove(enforcingAdmin.getUserId()); |
| } |
| } |
| |
| // TODO(b/263364434): centralise handling in one place. |
| // DPCs rely on a constant connection being established as soon as they are provisioned, |
| // so we shouldn't disconnect it even if they no longer have policies set. |
| if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { |
| return; |
| } |
| mDeviceAdminServiceController.stopServiceForAdmin( |
| enforcingAdmin.getPackageName(), |
| userId, |
| /* actionForLog= */ "policy-removed"); |
| } |
| |
| private boolean doesAdminHavePoliciesLocked(@NonNull EnforcingAdmin enforcingAdmin) { |
| for (PolicyKey policy : mGlobalPolicies.keySet()) { |
| PolicyState<?> policyState = mGlobalPolicies.get(policy); |
| if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) { |
| return true; |
| } |
| } |
| for (int i = 0; i < mLocalPolicies.size(); i++) { |
| for (PolicyKey policy : mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()) { |
| PolicyState<?> policyState = mLocalPolicies.get( |
| mLocalPolicies.keyAt(i)).get(policy); |
| if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Calculate the size of a policy in bytes |
| */ |
| |
| private static <V> int sizeOf(PolicyValue<V> value) { |
| try { |
| Parcel parcel = Parcel.obtain(); |
| parcel.writeParcelable(value, /* flags= */ 0); |
| |
| parcel.setDataPosition(0); |
| |
| byte[] bytes; |
| |
| bytes = parcel.marshall(); |
| return bytes.length; |
| } catch (Exception e) { |
| Log.e(TAG, "Error calculating size of policy: " + e); |
| return 0; |
| } |
| } |
| |
| /** |
| * Checks if the policy already exists and removes the current size to prevent recording the |
| * same policy twice. |
| * |
| * Checks if the new sum of the size of all policies is less than the maximum sum of policies |
| * size per admin and returns true. |
| * |
| * If the policy size limit is reached then send policy result to admin and return false. |
| */ |
| |
| private <V> boolean handleAdminPolicySizeLimit(PolicyState<V> policyState, EnforcingAdmin admin, |
| PolicyValue<V> value, PolicyDefinition policyDefinition, int userId) { |
| int currentSize = 0; |
| if (mAdminPolicySize.contains(admin.getUserId()) |
| && mAdminPolicySize.get( |
| admin.getUserId()).containsKey(admin)) { |
| currentSize = mAdminPolicySize.get(admin.getUserId()).get(admin); |
| } |
| if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { |
| currentSize -= sizeOf(policyState.getPoliciesSetByAdmins().get(admin)); |
| } |
| int policySize = sizeOf(value); |
| if (currentSize + policySize < POLICY_SIZE_LIMIT) { |
| increasePolicySizeForAdmin(admin, policySize); |
| return true; |
| } else { |
| sendPolicyResultToAdmin( |
| admin, |
| policyDefinition, |
| RESULT_FAILURE_STORAGE_LIMIT_REACHED, |
| userId); |
| return false; |
| } |
| } |
| |
| /** |
| * Increase the int in mAdminPolicySize representing the size of the sum of all |
| * active policies for that admin. |
| */ |
| |
| private <V> void increasePolicySizeForAdmin(EnforcingAdmin admin, int policySize) { |
| if (!mAdminPolicySize.contains(admin.getUserId())) { |
| mAdminPolicySize.put(admin.getUserId(), new HashMap<>()); |
| } |
| if (!mAdminPolicySize.get(admin.getUserId()).containsKey(admin)) { |
| mAdminPolicySize.get(admin.getUserId()).put(admin, /* size= */ 0); |
| } |
| mAdminPolicySize.get(admin.getUserId()).put(admin, |
| mAdminPolicySize.get(admin.getUserId()).get(admin) + policySize); |
| } |
| |
| /** |
| * Decrease the int in mAdminPolicySize representing the size of the sum of all |
| * active policies for that admin. |
| */ |
| |
| private <V> void decreasePolicySizeForAdmin(PolicyState<V> policyState, EnforcingAdmin admin) { |
| if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { |
| mAdminPolicySize.get(admin.getUserId()).put(admin, |
| mAdminPolicySize.get(admin.getUserId()).get(admin) - sizeOf( |
| policyState.getPoliciesSetByAdmins().get(admin))); |
| } |
| if (mAdminPolicySize.get(admin.getUserId()).get(admin) <= 0) { |
| mAdminPolicySize.get(admin.getUserId()).remove(admin); |
| } |
| if (mAdminPolicySize.get(admin.getUserId()).isEmpty()) { |
| mAdminPolicySize.remove(admin.getUserId()); |
| } |
| } |
| |
| @NonNull |
| private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) { |
| synchronized (mLock) { |
| return mEnforcingAdmins.contains(userId) |
| ? mEnforcingAdmins.get(userId) : Collections.emptySet(); |
| } |
| } |
| |
| private void write() { |
| synchronized (mLock) { |
| Log.d(TAG, "Writing device policies to file."); |
| new DevicePoliciesReaderWriter().writeToFileLocked(); |
| } |
| } |
| |
| // TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated |
| // and could result in a different enforced policy |
| void load() { |
| Log.d(TAG, "Reading device policies from file."); |
| synchronized (mLock) { |
| clear(); |
| new DevicePoliciesReaderWriter().readFromFileLocked(); |
| reapplyAllPoliciesLocked(); |
| } |
| } |
| |
| private <V> void reapplyAllPoliciesLocked() { |
| for (PolicyKey policy : mGlobalPolicies.keySet()) { |
| PolicyState<?> policyState = mGlobalPolicies.get(policy); |
| // Policy definition and value will always be of the same type |
| PolicyDefinition<V> policyDefinition = |
| (PolicyDefinition<V>) policyState.getPolicyDefinition(); |
| PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); |
| enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL); |
| } |
| for (int i = 0; i < mLocalPolicies.size(); i++) { |
| int userId = mLocalPolicies.keyAt(i); |
| for (PolicyKey policy : mLocalPolicies.get(userId).keySet()) { |
| PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy); |
| // Policy definition and value will always be of the same type |
| PolicyDefinition<V> policyDefinition = |
| (PolicyDefinition<V>) policyState.getPolicyDefinition(); |
| PolicyValue<V> policyValue = |
| (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); |
| enforcePolicy(policyDefinition, policyValue, userId); |
| |
| } |
| } |
| } |
| |
| /** |
| * Clear all policies set in the policy engine. |
| * |
| * <p>Note that this doesn't clear any enforcements, it only clears the data structures. |
| */ |
| void clearAllPolicies() { |
| clear(); |
| write(); |
| } |
| |
| private void clear() { |
| synchronized (mLock) { |
| mGlobalPolicies.clear(); |
| mLocalPolicies.clear(); |
| mEnforcingAdmins.clear(); |
| mAdminPolicySize.clear(); |
| } |
| } |
| |
| private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition, |
| @NonNull EnforcingAdmin enforcingAdmin) { |
| if (!policyDefinition.getPolicyKey().getIdentifier().equals( |
| CELLULAR_2G_USER_RESTRICTION_ID)) { |
| return false; |
| } |
| |
| boolean isCapabilitySupported; |
| try { |
| isCapabilitySupported = mContext.getSystemService( |
| TelephonyManager.class).isRadioInterfaceCapabilitySupported( |
| TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK); |
| } catch (IllegalStateException e) { |
| // isRadioInterfaceCapabilitySupported can throw if there is no Telephony |
| // service initialized. |
| isCapabilitySupported = false; |
| } |
| |
| if (!isCapabilitySupported) { |
| sendPolicyResultToAdmin( |
| enforcingAdmin, |
| policyDefinition, |
| RESULT_FAILURE_HARDWARE_LIMITATION, |
| UserHandle.USER_ALL); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private class DevicePoliciesReaderWriter { |
| private static final String DEVICE_POLICIES_XML = "device_policy_state.xml"; |
| 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_POLICY_STATE_ENTRY = "policy-state-entry"; |
| private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry"; |
| private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry"; |
| private static final String TAG_ENFORCING_ADMIN_AND_SIZE = "enforcing-admin-and-size"; |
| private static final String TAG_ENFORCING_ADMIN = "enforcing-admin"; |
| private static final String TAG_POLICY_SUM_SIZE = "policy-sum-size"; |
| private static final String ATTR_USER_ID = "user-id"; |
| private static final String ATTR_POLICY_SUM_SIZE = "size"; |
| |
| private final File mFile; |
| |
| private DevicePoliciesReaderWriter() { |
| mFile = new File(Environment.getDataSystemDirectory(), DEVICE_POLICIES_XML); |
| } |
| |
| void writeToFileLocked() { |
| Log.d(TAG, "Writing to " + mFile); |
| |
| AtomicFile f = new AtomicFile(mFile); |
| FileOutputStream outputStream = null; |
| try { |
| outputStream = f.startWrite(); |
| TypedXmlSerializer out = Xml.resolveSerializer(outputStream); |
| |
| out.startDocument(null, true); |
| |
| // Actual content |
| writeInner(out); |
| |
| out.endDocument(); |
| out.flush(); |
| |
| // Commit the content. |
| f.finishWrite(outputStream); |
| outputStream = null; |
| |
| } catch (IOException e) { |
| Log.e(TAG, "Exception when writing", e); |
| if (outputStream != null) { |
| f.failWrite(outputStream); |
| } |
| } |
| } |
| |
| // TODO(b/256846294): Add versioning to read/write |
| void writeInner(TypedXmlSerializer serializer) throws IOException { |
| writeLocalPoliciesInner(serializer); |
| writeGlobalPoliciesInner(serializer); |
| writeEnforcingAdminsInner(serializer); |
| writeEnforcingAdminSizeInner(serializer); |
| } |
| |
| 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<PolicyKey, PolicyState<?>> policy : mLocalPolicies.get( |
| userId).entrySet()) { |
| serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY); |
| |
| serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId); |
| |
| serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); |
| policy.getKey().saveToXml(serializer); |
| serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); |
| |
| serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); |
| policy.getValue().saveToXml(serializer); |
| serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); |
| |
| serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY); |
| } |
| } |
| } |
| } |
| |
| private void writeGlobalPoliciesInner(TypedXmlSerializer serializer) throws IOException { |
| if (mGlobalPolicies != null) { |
| for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) { |
| serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY); |
| |
| serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); |
| policy.getKey().saveToXml(serializer); |
| serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); |
| |
| serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); |
| policy.getValue().saveToXml(serializer); |
| serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); |
| |
| serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY); |
| } |
| } |
| } |
| |
| private void writeEnforcingAdminsInner(TypedXmlSerializer serializer) throws IOException { |
| if (mEnforcingAdmins != null) { |
| for (int i = 0; i < mEnforcingAdmins.size(); i++) { |
| int userId = mEnforcingAdmins.keyAt(i); |
| for (EnforcingAdmin admin : mEnforcingAdmins.get(userId)) { |
| serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMINS_ENTRY); |
| admin.saveToXml(serializer); |
| serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMINS_ENTRY); |
| } |
| } |
| } |
| } |
| |
| private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) |
| throws IOException { |
| if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { |
| if (mAdminPolicySize != null) { |
| for (int i = 0; i < mAdminPolicySize.size(); i++) { |
| int userId = mAdminPolicySize.keyAt(i); |
| for (EnforcingAdmin admin : mAdminPolicySize.get( |
| userId).keySet()) { |
| serializer.startTag(/* namespace= */ null, |
| TAG_ENFORCING_ADMIN_AND_SIZE); |
| serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); |
| admin.saveToXml(serializer); |
| serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); |
| serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); |
| serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE, |
| mAdminPolicySize.get(userId).get(admin)); |
| serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); |
| serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE); |
| } |
| } |
| } |
| } |
| } |
| |
| void readFromFileLocked() { |
| if (!mFile.exists()) { |
| Log.d(TAG, "" + mFile + " doesn't exist"); |
| return; |
| } |
| |
| Log.d(TAG, "Reading from " + mFile); |
| AtomicFile f = new AtomicFile(mFile); |
| InputStream input = null; |
| try { |
| input = f.openRead(); |
| TypedXmlPullParser parser = Xml.resolvePullParser(input); |
| |
| readInner(parser); |
| |
| } catch (XmlPullParserException | IOException | ClassNotFoundException e) { |
| Slogf.wtf(TAG, "Error parsing resources file", e); |
| } finally { |
| IoUtils.closeQuietly(input); |
| } |
| } |
| |
| private void readInner(TypedXmlPullParser parser) |
| throws IOException, XmlPullParserException, ClassNotFoundException { |
| int outerDepth = parser.getDepth(); |
| while (XmlUtils.nextElementWithin(parser, outerDepth)) { |
| String tag = parser.getName(); |
| switch (tag) { |
| case TAG_LOCAL_POLICY_ENTRY: |
| readLocalPoliciesInner(parser); |
| break; |
| case TAG_GLOBAL_POLICY_ENTRY: |
| readGlobalPoliciesInner(parser); |
| break; |
| case TAG_ENFORCING_ADMINS_ENTRY: |
| readEnforcingAdminsInner(parser); |
| break; |
| case TAG_ENFORCING_ADMIN_AND_SIZE: |
| readEnforcingAdminAndSizeInner(parser); |
| break; |
| default: |
| Slogf.wtf(TAG, "Unknown tag " + tag); |
| } |
| } |
| } |
| |
| private void readLocalPoliciesInner(TypedXmlPullParser parser) |
| throws XmlPullParserException, IOException { |
| int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID); |
| PolicyKey policyKey = null; |
| PolicyState<?> policyState = null; |
| int outerDepth = parser.getDepth(); |
| while (XmlUtils.nextElementWithin(parser, outerDepth)) { |
| String tag = parser.getName(); |
| switch (tag) { |
| case TAG_POLICY_KEY_ENTRY: |
| policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); |
| break; |
| case TAG_POLICY_STATE_ENTRY: |
| policyState = PolicyState.readFromXml(parser); |
| break; |
| default: |
| Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag); |
| } |
| } |
| |
| if (policyKey != null && policyState != null) { |
| if (!mLocalPolicies.contains(userId)) { |
| mLocalPolicies.put(userId, new HashMap<>()); |
| } |
| mLocalPolicies.get(userId).put(policyKey, policyState); |
| } else { |
| Slogf.wtf(TAG, "Error parsing local policy, policyKey is " |
| + (policyKey == null ? "null" : policyKey) + ", and policyState is " |
| + (policyState == null ? "null" : policyState) + "."); |
| } |
| } |
| |
| private void readGlobalPoliciesInner(TypedXmlPullParser parser) |
| throws IOException, XmlPullParserException { |
| PolicyKey policyKey = null; |
| PolicyState<?> policyState = null; |
| int outerDepth = parser.getDepth(); |
| while (XmlUtils.nextElementWithin(parser, outerDepth)) { |
| String tag = parser.getName(); |
| switch (tag) { |
| case TAG_POLICY_KEY_ENTRY: |
| policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); |
| break; |
| case TAG_POLICY_STATE_ENTRY: |
| policyState = PolicyState.readFromXml(parser); |
| break; |
| default: |
| Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag); |
| } |
| } |
| |
| if (policyKey != null && policyState != null) { |
| mGlobalPolicies.put(policyKey, policyState); |
| } else { |
| Slogf.wtf(TAG, "Error parsing global policy, policyKey is " |
| + (policyKey == null ? "null" : policyKey) + ", and policyState is " |
| + (policyState == null ? "null" : policyState) + "."); |
| } |
| } |
| |
| private void readEnforcingAdminsInner(TypedXmlPullParser parser) |
| throws XmlPullParserException { |
| EnforcingAdmin admin = EnforcingAdmin.readFromXml(parser); |
| if (admin == null) { |
| Slogf.wtf(TAG, "Error parsing enforcingAdmins, EnforcingAdmin is null."); |
| return; |
| } |
| if (!mEnforcingAdmins.contains(admin.getUserId())) { |
| mEnforcingAdmins.put(admin.getUserId(), new HashSet<>()); |
| } |
| mEnforcingAdmins.get(admin.getUserId()).add(admin); |
| } |
| |
| private void readEnforcingAdminAndSizeInner(TypedXmlPullParser parser) |
| throws XmlPullParserException, IOException { |
| int outerDepth = parser.getDepth(); |
| EnforcingAdmin admin = null; |
| int size = 0; |
| while (XmlUtils.nextElementWithin(parser, outerDepth)) { |
| String tag = parser.getName(); |
| switch (tag) { |
| case TAG_ENFORCING_ADMIN: |
| admin = EnforcingAdmin.readFromXml(parser); |
| break; |
| case TAG_POLICY_SUM_SIZE: |
| size = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE); |
| break; |
| default: |
| Slogf.wtf(TAG, "Unknown tag " + tag); |
| } |
| } |
| if (admin == null) { |
| Slogf.wtf(TAG, "Error parsing enforcingAdmins, EnforcingAdmin is null."); |
| return; |
| } |
| if (size <= 0) { |
| Slogf.wtf(TAG, "Error parsing policy size, size is " + size); |
| return; |
| } |
| if (!mAdminPolicySize.contains(admin.getUserId())) { |
| mAdminPolicySize.put(admin.getUserId(), new HashMap<>()); |
| } |
| mAdminPolicySize.get(admin.getUserId()).put(admin, size); |
| } |
| } |
| } |