summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Rubin Xu <rubinxu@google.com> 2024-03-21 13:02:13 +0000
committer Rubin Xu <rubinxu@google.com> 2024-04-03 12:53:48 +0100
commit6a152f70a08333fa7be2b68b27824270e9a3cda0 (patch)
treeff06732c1b3b08a44652366b1c50e48fd1138e8e
parentc6d3da582a0d92f3c7a23c5a9ceb48ae6c988df9 (diff)
Enable device management role holder to set app restrictions
Give DMRH the ability to set app restrictions on any target applications in the calling user, as well as the parent user of a COPE profile. App restrictions will be automatically propagated to the private space if they are set on the parent user of a COPE profile. App restrictions set by the DMRH are handled by policy engine as coexisable. Existing app restrictions set by DPC are kept untouched so they are mostly indepenent from the ones set by the DMRH. Bug: 304969881 Test: ApplicationRestrictionsTest Change-Id: Iffb8a0b4a7bf14350f52676d5d7d9d75a1c96393
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java35
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java123
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;
});
}