Change bypassRoleQualification persistence logic
Fixes: 228596883
Test: atest android.devicepolicy.cts.DevicePolicyManagementRoleHolderTest
Test: atest android.devicepolicy.cts.DevicePolicyManagerTest
Change-Id: Ife9e27eaed28af41df77ef9669030fe71392c457
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 9a0b5c7..48a436f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -69,6 +69,7 @@
private static final String TAG_PASSWORD_VALIDITY = "password-validity";
private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
+ private static final String TAG_BYPASS_ROLE_QUALIFICATIONS = "bypass-role-qualifications";
private static final String ATTR_VALUE = "value";
private static final String ATTR_ALIAS = "alias";
private static final String ATTR_ID = "id";
@@ -107,6 +108,8 @@
int mPasswordOwner = -1;
long mLastMaximumTimeToLock = -1;
boolean mUserSetupComplete = false;
+ boolean mBypassDevicePolicyManagementRoleQualifications = false;
+ String mCurrentRoleHolder;
boolean mPaired = false;
int mUserProvisioningState;
int mPermissionPolicy;
@@ -374,6 +377,12 @@
out.endTag(null, TAG_APPS_SUSPENDED);
}
+ if (policyData.mBypassDevicePolicyManagementRoleQualifications) {
+ out.startTag(null, TAG_BYPASS_ROLE_QUALIFICATIONS);
+ out.attribute(null, ATTR_VALUE, policyData.mCurrentRoleHolder);
+ out.endTag(null, TAG_BYPASS_ROLE_QUALIFICATIONS);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -558,6 +567,9 @@
} else if (TAG_APPS_SUSPENDED.equals(tag)) {
policy.mAppsSuspended =
parser.getAttributeBoolean(null, ATTR_VALUE, false);
+ } else if (TAG_BYPASS_ROLE_QUALIFICATIONS.equals(tag)) {
+ policy.mBypassDevicePolicyManagementRoleQualifications = true;
+ policy.mCurrentRoleHolder = parser.getAttributeValue(null, ATTR_VALUE);
} else {
Slogf.w(TAG, "Unknown tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 18bffeb..2ffda6c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -138,7 +138,6 @@
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.provider.Settings.Global.BYPASS_DEVICE_POLICY_MANAGEMENT_ROLE_QUALIFICATIONS;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -222,6 +221,7 @@
import android.app.admin.WifiSsidPolicy;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
+import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -255,6 +255,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Resources;
@@ -409,6 +410,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -756,6 +758,7 @@
private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL;
private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider;
+ private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver;
private static final boolean ENABLE_LOCK_GUARD = true;
@@ -1823,6 +1826,8 @@
mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector);
mDeviceManagementResourcesProvider = mInjector.getDeviceManagementResourcesProvider();
+ mDevicePolicyManagementRoleObserver = new DevicePolicyManagementRoleObserver(mContext);
+ mDevicePolicyManagementRoleObserver.register();
// "Lite" interface is available even when the device doesn't have the feature
LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService);
@@ -18627,16 +18632,11 @@
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLE_HOLDERS));
return mInjector.binderWithCleanCallingIdentity(() -> {
- if (mInjector.settingsGlobalGetInt(
- BYPASS_DEVICE_POLICY_MANAGEMENT_ROLE_QUALIFICATIONS, /* def= */ 0) == 1) {
+ if (getUserData(
+ UserHandle.USER_SYSTEM).mBypassDevicePolicyManagementRoleQualifications) {
return true;
}
- if (shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal()) {
- mInjector.settingsGlobalPutInt(
- BYPASS_DEVICE_POLICY_MANAGEMENT_ROLE_QUALIFICATIONS, /* value= */ 1);
- return true;
- }
- return false;
+ return shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal();
});
}
@@ -18649,6 +18649,142 @@
return accounts.length == 0;
}
+ private void setBypassDevicePolicyManagementRoleQualificationStateInternal(
+ String currentRoleHolder, boolean allowBypass) {
+ boolean stateChanged = false;
+ DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
+ if (policy.mBypassDevicePolicyManagementRoleQualifications != allowBypass) {
+ policy.mBypassDevicePolicyManagementRoleQualifications = allowBypass;
+ stateChanged = true;
+ }
+ if (!Objects.equals(currentRoleHolder, policy.mCurrentRoleHolder)) {
+ policy.mCurrentRoleHolder = currentRoleHolder;
+ stateChanged = true;
+ }
+ if (stateChanged) {
+ synchronized (getLockObject()) {
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
+ }
+ }
+ }
+
+ private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener {
+ private RoleManager mRm;
+ private final Executor mExecutor;
+ private final Context mContext;
+
+ DevicePolicyManagementRoleObserver(@NonNull Context context) {
+ mContext = context;
+ mExecutor = mContext.getMainExecutor();
+ mRm = mContext.getSystemService(RoleManager.class);
+ }
+
+ public void register() {
+ mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.SYSTEM);
+ }
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ if (!RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+ return;
+ }
+ String newRoleHolder = getRoleHolder();
+ if (isDefaultRoleHolder(newRoleHolder)) {
+ Slogf.i(LOG_TAG,
+ "onRoleHoldersChanged: Default role holder is set, returning early");
+ return;
+ }
+ if (newRoleHolder == null) {
+ Slogf.i(LOG_TAG,
+ "onRoleHoldersChanged: New role holder is null, returning early");
+ return;
+ }
+ if (shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal()) {
+ Slogf.w(LOG_TAG,
+ "onRoleHoldersChanged: Updating current role holder to " + newRoleHolder);
+ setBypassDevicePolicyManagementRoleQualificationStateInternal(
+ newRoleHolder, /* allowBypass= */ true);
+ return;
+ }
+ DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
+ if (!newRoleHolder.equals(policy.mCurrentRoleHolder)) {
+ Slogf.w(LOG_TAG,
+ "onRoleHoldersChanged: You can't set a different role holder, role "
+ + "is getting revoked from " + newRoleHolder);
+ setBypassDevicePolicyManagementRoleQualificationStateInternal(
+ /* currentRoleHolder= */ null, /* allowBypass= */ false);
+ mRm.removeRoleHolderAsUser(
+ RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT,
+ newRoleHolder,
+ /* flags= */ 0,
+ user,
+ mExecutor,
+ successful -> {});
+ }
+ }
+
+ private String getRoleHolder() {
+ return DevicePolicyManagerService.this.getDevicePolicyManagementRoleHolderPackageName(
+ mContext);
+ }
+
+ private boolean isDefaultRoleHolder(String packageName) {
+ String defaultRoleHolder = getDefaultRoleHolderPackageName();
+ if (packageName == null || defaultRoleHolder == null) {
+ return false;
+ }
+ if (!defaultRoleHolder.equals(packageName)) {
+ return false;
+ }
+ return hasSigningCertificate(
+ packageName, getDefaultRoleHolderPackageSignature());
+ }
+
+ private boolean hasSigningCertificate(String packageName, String certificateString) {
+ if (packageName == null || certificateString == null) {
+ return false;
+ }
+ byte[] certificate;
+ try {
+ certificate = new Signature(certificateString).toByteArray();
+ } catch (IllegalArgumentException e) {
+ Slogf.w(LOG_TAG, "Cannot parse signing certificate: " + certificateString, e);
+ return false;
+ }
+ PackageManager pm = mInjector.getPackageManager();
+ return pm.hasSigningCertificate(
+ packageName, certificate, PackageManager.CERT_INPUT_SHA256);
+ }
+
+ private String getDefaultRoleHolderPackageName() {
+ String[] info = getDefaultRoleHolderPackageNameAndSignature();
+ if (info == null) {
+ return null;
+ }
+ return info[0];
+ }
+
+ private String getDefaultRoleHolderPackageSignature() {
+ String[] info = getDefaultRoleHolderPackageNameAndSignature();
+ if (info == null || info.length < 2) {
+ return null;
+ }
+ return info[1];
+ }
+
+ private String[] getDefaultRoleHolderPackageNameAndSignature() {
+ String packageNameAndSignature = mContext.getString(
+ com.android.internal.R.string.config_devicePolicyManagement);
+ if (TextUtils.isEmpty(packageNameAndSignature)) {
+ return null;
+ }
+ if (packageNameAndSignature.contains(":")) {
+ return packageNameAndSignature.split(":");
+ }
+ return new String[]{packageNameAndSignature};
+ }
+ }
+
@Override
public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 449177e..0afb182 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -102,6 +102,7 @@
<uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 2cf67f8..e991ec6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -237,6 +237,8 @@
return mMockSystemServices.devicePolicyManager;
case Context.LOCATION_SERVICE:
return mMockSystemServices.locationManager;
+ case Context.ROLE_SERVICE:
+ return mMockSystemServices.roleManager;
}
throw new UnsupportedOperationException();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 34c9f7c..884ffce 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -33,6 +33,7 @@
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.IBackupManager;
+import android.app.role.RoleManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -131,6 +132,7 @@
public final VpnManager vpnManager;
public final DevicePolicyManager devicePolicyManager;
public final LocationManager locationManager;
+ public final RoleManager roleManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -181,6 +183,7 @@
vpnManager = mock(VpnManager.class);
devicePolicyManager = mock(DevicePolicyManager.class);
locationManager = mock(LocationManager.class);
+ roleManager = realContext.getSystemService(RoleManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());