Send EXTRA_USER with DevicePolicy lock broadcasts
DeviceAdmins inside profiles may receive broadcasts referring either
to the parent profile or to themselves.
We need a way to differentiate that.
Same commit fixes a bug in DevicePolicyManagerTest where USER_SYSTEM
is returned twice in getProfiles() when called for a managed profile of
USER_SYSTEM. This does not happen in the real API.
Bug: 30185351
Bug: 31001762
Test: runtest -x services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
Change-Id: Iea2735357f4019b2b81b6784e7ea6aead63f2636
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e5c8d02..7538c95 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2213,15 +2213,13 @@
/**
* Send an update to all admins of a user that enforce a specified policy.
*/
- void sendAdminCommandLocked(String action, int reqPolicy, int userHandle) {
+ void sendAdminCommandLocked(String action, int reqPolicy, int userHandle, Bundle adminExtras) {
final DevicePolicyData policy = getUserData(userHandle);
final int count = policy.mAdminList.size();
- if (count > 0) {
- for (int i = 0; i < count; i++) {
- final ActiveAdmin admin = policy.mAdminList.get(i);
- if (admin.info.usesPolicy(reqPolicy)) {
- sendAdminCommandLocked(admin, action);
- }
+ for (int i = 0; i < count; i++) {
+ final ActiveAdmin admin = policy.mAdminList.get(i);
+ if (admin.info.usesPolicy(reqPolicy)) {
+ sendAdminCommandLocked(admin, action, adminExtras, null);
}
}
}
@@ -2231,10 +2229,10 @@
* enforce a specified policy.
*/
private void sendAdminCommandToSelfAndProfilesLocked(String action, int reqPolicy,
- int userHandle) {
+ int userHandle, Bundle adminExtras) {
int[] profileIds = mUserManager.getProfileIdsWithDisabled(userHandle);
for (int profileId : profileIds) {
- sendAdminCommandLocked(action, reqPolicy, profileId);
+ sendAdminCommandLocked(action, reqPolicy, profileId, adminExtras);
}
}
@@ -2243,10 +2241,12 @@
*/
private void sendAdminCommandForLockscreenPoliciesLocked(
String action, int reqPolicy, int userHandle) {
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle));
if (isSeparateProfileChallengeEnabled(userHandle)) {
- sendAdminCommandLocked(action, reqPolicy, userHandle);
+ sendAdminCommandLocked(action, reqPolicy, userHandle, extras);
} else {
- sendAdminCommandToSelfAndProfilesLocked(action, reqPolicy, userHandle);
+ sendAdminCommandToSelfAndProfilesLocked(action, reqPolicy, userHandle, extras);
}
}
@@ -2796,6 +2796,9 @@
}
private void handlePasswordExpirationNotification(int userHandle) {
+ final Bundle adminExtras = new Bundle();
+ adminExtras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle));
+
synchronized (this) {
final long now = System.currentTimeMillis();
@@ -2809,7 +2812,7 @@
&& now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
&& admin.passwordExpirationDate > 0L) {
sendAdminCommandLocked(admin,
- DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING);
+ DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING, adminExtras, null);
}
}
setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d392459..c5e343e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -71,6 +71,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -544,6 +545,83 @@
}
/**
+ * Test for: @{link DevicePolicyManager#setActivePasswordState}
+ *
+ * Validates that when the password for a user changes, the notification broadcast intent
+ * {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is sent to managed profile owners, in
+ * addition to ones in the original user.
+ */
+ public void testSetActivePasswordState_sendToProfiles() throws Exception {
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+
+ final int MANAGED_PROFILE_USER_ID = 78;
+ final int MANAGED_PROFILE_ADMIN_UID =
+ UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+
+ // Setup device owner.
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.packageName = admin1.getPackageName();
+ setupDeviceOwner();
+
+ // Add a managed profile belonging to the system user.
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+ // Change the parent user's password.
+ dpm.reportPasswordChanged(UserHandle.USER_SYSTEM);
+
+ // Both the device owner and the managed profile owner should receive this broadcast.
+ final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
+ intent.setComponent(admin1);
+ intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM));
+
+ verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
+ MockUtils.checkIntent(intent),
+ MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+ verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
+ MockUtils.checkIntent(intent),
+ MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
+ }
+
+ /**
+ * Test for: @{link DevicePolicyManager#setActivePasswordState}
+ *
+ * Validates that when the password for a managed profile changes, the notification broadcast
+ * intent {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is only sent to the profile, not
+ * its parent.
+ */
+ public void testSetActivePasswordState_notSentToParent() throws Exception {
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+
+ final int MANAGED_PROFILE_USER_ID = 78;
+ final int MANAGED_PROFILE_ADMIN_UID =
+ UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+
+ // Setup device owner.
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.packageName = admin1.getPackageName();
+ doReturn(true).when(mContext.lockPatternUtils)
+ .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID);
+ setupDeviceOwner();
+
+ // Add a managed profile belonging to the system user.
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+ // Change the profile's password.
+ dpm.reportPasswordChanged(MANAGED_PROFILE_USER_ID);
+
+ // Both the device owner and the managed profile owner should receive this broadcast.
+ final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
+ intent.setComponent(admin1);
+ intent.putExtra(Intent.EXTRA_USER, UserHandle.of(MANAGED_PROFILE_USER_ID));
+
+ verify(mContext.spiedContext, never()).sendBroadcastAsUser(
+ MockUtils.checkIntent(intent),
+ MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+ verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
+ MockUtils.checkIntent(intent),
+ MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
+ }
+ /**
* Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully.
*/
public void testSetDeviceOwner() throws Exception {
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 65255d9..a1b6769 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -18,6 +18,7 @@
import android.accounts.Account;
import android.accounts.AccountManager;
+import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.backup.IBackupManager;
@@ -276,6 +277,7 @@
public final MockContentResolver contentResolver;
public final TelephonyManager telephonyManager;
public final AccountManager accountManager;
+ public final AlarmManager alarmManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
@@ -319,6 +321,7 @@
settings = mock(SettingsForMock.class);
telephonyManager = mock(TelephonyManager.class);
accountManager = mock(AccountManager.class);
+ alarmManager = mock(AlarmManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(context.getPackageManager());
@@ -327,7 +330,8 @@
contentResolver = new MockContentResolver();
- // Add the system user
+ // Add the system user with a fake profile group already set up (this can happen in the real
+ // world if a managed profile is added and then removed).
systemUserDataDir =
addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY, UserHandle.USER_SYSTEM);
@@ -410,9 +414,9 @@
if (parent == null) {
return ret;
}
- ret.add(parent);
for (UserInfo ui : mUserInfos) {
- if (ui.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ if (ui == parent
+ || ui.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
&& ui.profileGroupId == parent.profileGroupId) {
ret.add(ui);
}
@@ -463,6 +467,8 @@
@Override
public Object getSystemService(String name) {
switch (name) {
+ case Context.ALARM_SERVICE:
+ return alarmManager;
case Context.USER_SERVICE:
return userManager;
case Context.POWER_SERVICE: