diff options
5 files changed, 148 insertions, 25 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 60dffbd0e421..2f63dbb1d8f4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -10285,6 +10285,16 @@ public class DevicePolicyManager { * get the list of app restrictions set by each admin via * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin}. * + * <p>Starting from Android Version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * the device policy management role holder can also set app restrictions on any applications + * in the calling user, as well as the parent user of an organization-owned managed profile via + * the {@link DevicePolicyManager} instance returned by + * {@link #getParentProfileInstance(ComponentName)}. App restrictions set by the device policy + * management role holder are not returned by + * {@link UserManager#getApplicationRestrictions(String)}. The target application should use + * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} to retrieve + * them, alongside any app restrictions the profile or device owner might have set. + * * <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread * * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or @@ -10300,11 +10310,14 @@ public class DevicePolicyManager { @WorkerThread public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { - throwIfParentInstance("setApplicationRestrictions"); + if (!Flags.dmrhCanSetAppRestriction()) { + throwIfParentInstance("setApplicationRestrictions"); + } + if (mService != null) { try { mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName, - settings); + settings, mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -11705,11 +11718,14 @@ public class DevicePolicyManager { @WorkerThread public @NonNull Bundle getApplicationRestrictions( @Nullable ComponentName admin, String packageName) { - throwIfParentInstance("getApplicationRestrictions"); + if (!Flags.dmrhCanSetAppRestriction()) { + throwIfParentInstance("getApplicationRestrictions"); + } + if (mService != null) { try { return mService.getApplicationRestrictions(admin, mContext.getPackageName(), - packageName); + packageName, mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -13989,8 +14005,15 @@ public class DevicePolicyManager { public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); try { - if (!mService.isManagedProfile(admin)) { - throw new SecurityException("The current user does not have a parent profile."); + if (Flags.dmrhCanSetAppRestriction()) { + UserManager um = mContext.getSystemService(UserManager.class); + if (!um.isManagedProfile()) { + throw new SecurityException("The current user does not have a parent profile."); + } + } else { + if (!mService.isManagedProfile(admin)) { + throw new SecurityException("The current user does not have a parent profile."); + } } return new DevicePolicyManager(mContext, mService, true); } catch (RemoteException e) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d4589dc6d453..2002326d76bd 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -244,8 +244,8 @@ interface IDevicePolicyManager { void setDefaultSmsApplication(in ComponentName admin, String callerPackageName, String packageName, boolean parent); void setDefaultDialerApplication(String packageName); - void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings); - Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName); + void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings, in boolean parent); + Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in boolean parent); boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName); String getApplicationRestrictionsManagingPackage(in ComponentName admin); boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 4fa45be57a11..cca0b14904ca 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -191,6 +191,13 @@ flag { } flag { + name: "dmrh_can_set_app_restriction" + namespace: "enterprise" + description: "Allow DMRH to set application restrictions (both on the profile and the parent)" + bug: "328758346" +} + +flag { name: "allow_screen_brightness_control_on_cope" namespace: "enterprise" description: "Allow COPE admin to control screen brightness and timeout." diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java index 82f9aadba9f4..d24afabe95a4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java @@ -92,7 +92,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_VALUE)) { - values.add(parser.nextText().trim()); + values.add(parser.nextText()); count--; } } @@ -111,7 +111,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { restrictions.putParcelableArray(key, bundleList.toArray(new Bundle[bundleList.size()])); } else { - String value = parser.nextText().trim(); + String value = parser.nextText(); if (ATTR_TYPE_BOOLEAN.equals(valType)) { restrictions.putBoolean(key, Boolean.parseBoolean(value)); } else if (ATTR_TYPE_INTEGER.equals(valType)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f955b91136a3..ec9197f5343c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11502,10 +11502,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setApplicationRestrictions(ComponentName who, String callerPackage, - String packageName, Bundle restrictions) { + String packageName, Bundle restrictions, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS); + // This check is eventually made in UMS, checking here to fail early. + String validationResult = + FrameworkParsingPackageUtils.validateName(packageName, false, false); + if (validationResult != null) { + throw new IllegalArgumentException("Invalid package name: " + validationResult); + } + if (isUnicornFlagEnabled()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( who, @@ -11513,12 +11520,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), caller.getUserId() ); - // This check is eventually made in UMS, checking here to fail early. - String validationResult = - FrameworkParsingPackageUtils.validateName(packageName, false, false); - if (validationResult != null) { - throw new IllegalArgumentException("Invalid package name: " + validationResult); - } if (restrictions == null || restrictions.isEmpty()) { mDevicePolicyEngine.removeLocalPolicy( @@ -11534,6 +11535,57 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } setBackwardsCompatibleAppRestrictions( caller, packageName, restrictions, caller.getUserHandle()); + } else if (Flags.dmrhCanSetAppRestriction()) { + final boolean isRoleHolder; + if (who != null) { + // DO or PO + Preconditions.checkCallAuthorization( + (isProfileOwner(caller) || isDefaultDeviceOwner(caller))); + Preconditions.checkCallAuthorization(!parent, + "DO or PO cannot call this on parent"); + // Caller has opted to be treated as DPC (by passing a non-null who), so don't + // consider it as the DMRH, even if the caller is both the DPC and the DMRH. + isRoleHolder = false; + } else { + // Delegates, or the DMRH. Only DMRH can call this on COPE parent + isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller); + if (parent) { + Preconditions.checkCallAuthorization(isRoleHolder); + Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(), + "Role Holder can only operate parent app restriction on COPE devices"); + } else { + Preconditions.checkCallAuthorization(isRoleHolder + || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + } + } + // DMRH caller uses policy engine, others still use legacy code path + if (isRoleHolder) { + EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null, + caller.getPackageName()); + int affectedUserId = parent + ? getProfileParentId(caller.getUserId()) : caller.getUserId(); + if (restrictions == null || restrictions.isEmpty()) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + enforcingAdmin, + affectedUserId); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + enforcingAdmin, + new BundlePolicyValue(restrictions), + affectedUserId); + } + Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + changeIntent.setPackage(packageName); + changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId)); + } else { + mInjector.binderWithCleanCallingIdentity(() -> { + mUserManager.setApplicationRestrictions(packageName, restrictions, + caller.getUserHandle()); + }); + } } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) @@ -12865,7 +12917,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public Bundle getApplicationRestrictions(ComponentName who, String callerPackage, - String packageName) { + String packageName, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); if (isUnicornFlagEnabled()) { @@ -12884,6 +12936,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Bundle.EMPTY; } return policies.get(enforcingAdmin).getValue(); + } else if (Flags.dmrhCanSetAppRestriction()) { + final boolean isRoleHolder; + if (who != null) { + // Caller is DO or PO. They cannot call this on parent + Preconditions.checkCallAuthorization(!parent + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))); + // Caller has opted to be treated as DPC (by passing a non-null who), so don't + // consider it as the DMRH, even if the caller is both the DPC and the DMRH. + isRoleHolder = false; + } else { + // Caller is delegates or the DMRH. Only DMRH can call this on parent + isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller); + if (parent) { + Preconditions.checkCallAuthorization(isRoleHolder); + Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(), + "Role Holder can only operate parent app restriction on COPE devices"); + } else { + Preconditions.checkCallAuthorization(isRoleHolder + || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + } + } + if (isRoleHolder) { + EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null, + caller.getPackageName()); + int affectedUserId = parent + ? getProfileParentId(caller.getUserId()) : caller.getUserId(); + LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + affectedUserId); + if (!policies.containsKey(enforcingAdmin)) { + return Bundle.EMPTY; + } + return policies.get(enforcingAdmin).getValue(); + } else { + return mInjector.binderWithCleanCallingIdentity(() -> { + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, + caller.getUserHandle()); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; + }); + } + } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) @@ -15804,19 +15900,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { for (EnforcingAdmin admin : policies.keySet()) { restrictions.add(policies.get(admin).getValue()); } - if (!restrictions.isEmpty()) { - return restrictions; - } return mInjector.binderWithCleanCallingIdentity(() -> { - // Could be a device that has a DPC that hasn't migrated yet, so just return any + // Could be a device that has a DPC that hasn't migrated yet, so also return any // restrictions saved in userManager. Bundle bundle = mUserManager.getApplicationRestrictions( packageName, UserHandle.of(userId)); - if (bundle == null || bundle.isEmpty()) { - return new ArrayList<>(); + if (bundle != null && !bundle.isEmpty()) { + restrictions.add(bundle); } - return List.of(bundle); + return restrictions; }); } |