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/api/current.txt b/api/current.txt
index b1ba9c8..83b71f5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6024,10 +6024,14 @@
     method public void onLockTaskModeEntering(android.content.Context, android.content.Intent, java.lang.String);
     method public void onLockTaskModeExiting(android.content.Context, android.content.Intent);
     method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int);
-    method public void onPasswordChanged(android.content.Context, android.content.Intent);
-    method public void onPasswordExpiring(android.content.Context, android.content.Intent);
-    method public void onPasswordFailed(android.content.Context, android.content.Intent);
-    method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
+    method public deprecated void onPasswordChanged(android.content.Context, android.content.Intent);
+    method public void onPasswordChanged(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordExpiring(android.content.Context, android.content.Intent);
+    method public void onPasswordExpiring(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordFailed(android.content.Context, android.content.Intent);
+    method public void onPasswordFailed(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordSucceeded(android.content.Context, android.content.Intent);
+    method public void onPasswordSucceeded(android.content.Context, android.content.Intent, android.os.UserHandle);
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
diff --git a/api/system-current.txt b/api/system-current.txt
index 92b437d..4538241 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6202,10 +6202,14 @@
     method public void onLockTaskModeEntering(android.content.Context, android.content.Intent, java.lang.String);
     method public void onLockTaskModeExiting(android.content.Context, android.content.Intent);
     method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int);
-    method public void onPasswordChanged(android.content.Context, android.content.Intent);
-    method public void onPasswordExpiring(android.content.Context, android.content.Intent);
-    method public void onPasswordFailed(android.content.Context, android.content.Intent);
-    method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
+    method public deprecated void onPasswordChanged(android.content.Context, android.content.Intent);
+    method public void onPasswordChanged(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordExpiring(android.content.Context, android.content.Intent);
+    method public void onPasswordExpiring(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordFailed(android.content.Context, android.content.Intent);
+    method public void onPasswordFailed(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordSucceeded(android.content.Context, android.content.Intent);
+    method public void onPasswordSucceeded(android.content.Context, android.content.Intent, android.os.UserHandle);
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
diff --git a/api/test-current.txt b/api/test-current.txt
index 9b8e87c..54b2830 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6041,10 +6041,14 @@
     method public void onLockTaskModeEntering(android.content.Context, android.content.Intent, java.lang.String);
     method public void onLockTaskModeExiting(android.content.Context, android.content.Intent);
     method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int);
-    method public void onPasswordChanged(android.content.Context, android.content.Intent);
-    method public void onPasswordExpiring(android.content.Context, android.content.Intent);
-    method public void onPasswordFailed(android.content.Context, android.content.Intent);
-    method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
+    method public deprecated void onPasswordChanged(android.content.Context, android.content.Intent);
+    method public void onPasswordChanged(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordExpiring(android.content.Context, android.content.Intent);
+    method public void onPasswordExpiring(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordFailed(android.content.Context, android.content.Intent);
+    method public void onPasswordFailed(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public deprecated void onPasswordSucceeded(android.content.Context, android.content.Intent);
+    method public void onPasswordSucceeded(android.content.Context, android.content.Intent, android.os.UserHandle);
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index aae80ed..f2a8777 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.security.KeyChain;
 
@@ -123,7 +124,7 @@
      * of the new password with {@link DevicePolicyManager#isActivePasswordSufficient()
      * DevicePolicyManager.isActivePasswordSufficient()}.
      * You will generally
-     * handle this in {@link DeviceAdminReceiver#onPasswordChanged}.
+     * handle this in {@link DeviceAdminReceiver#onPasswordChanged(Context, Intent, UserHandle)}.
      *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive
@@ -139,7 +140,7 @@
      * number of failed password attempts there have been with
      * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts
      * DevicePolicyManager.getCurrentFailedPasswordAttempts()}.  You will generally
-     * handle this in {@link DeviceAdminReceiver#onPasswordFailed}.
+     * handle this in {@link DeviceAdminReceiver#onPasswordFailed(Context, Intent, UserHandle)}.
      *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
@@ -152,7 +153,7 @@
     /**
      * Action sent to a device administrator when the user has successfully entered their device
      * or profile challenge password, after failing one or more times.  You will generally
-     * handle this in {@link DeviceAdminReceiver#onPasswordSucceeded}.
+     * handle this in {@link DeviceAdminReceiver#onPasswordSucceeded(Context, Intent, UserHandle)}.
      *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
@@ -165,7 +166,7 @@
     /**
      * Action periodically sent to a device administrator when the device or profile challenge
      * password is expiring.  You will generally
-     * handle this in {@link DeviceAdminReceiver#onPasswordExpiring}.
+     * handle this in {@link DeviceAdminReceiver#onPasswordExpiring(Context, Intent, UserHandle)}.
      *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} to receive
@@ -497,33 +498,90 @@
      * to retrieve the active password characteristics.
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use
+     *             {@link #onPasswordChanged(Context, Intent, UserHandle)} instead.
      */
+    @Deprecated
     public void onPasswordChanged(Context context, Intent intent) {
     }
 
     /**
+     * Called after the user has changed their device or profile challenge password, as a result of
+     * receiving {@link #ACTION_PASSWORD_CHANGED}.  At this point you
+     * can use {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+     * to retrieve the active password characteristics.
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param user The user or profile for whom the password changed. To see whether this
+     *        user is the current profile or a parent user, check for equality with
+     *        {@link Process#myUserHandle}.
+     */
+    public void onPasswordChanged(Context context, Intent intent, UserHandle user) {
+        onPasswordChanged(context, intent);
+    }
+
+    /**
      * Called after the user has failed at entering their device or profile challenge password,
      * as a result of receiving {@link #ACTION_PASSWORD_FAILED}.  At this point you can use
      * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()} to retrieve the number of
      * failed password attempts.
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use
+     *             {@link #onPasswordFailed(Context, Intent, UserHandle)} instead.
      */
+    @Deprecated
     public void onPasswordFailed(Context context, Intent intent) {
     }
 
     /**
+     * Called after the user has failed at entering their device or profile challenge password,
+     * as a result of receiving {@link #ACTION_PASSWORD_FAILED}.  At this point you can use
+     * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()} to retrieve the number of
+     * failed password attempts.
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param user The user or profile for whom the password check failed. To see whether this
+     *        user is the current profile or a parent user, check for equality with
+     *        {@link Process#myUserHandle}.
+     */
+    public void onPasswordFailed(Context context, Intent intent, UserHandle user) {
+        onPasswordFailed(context, intent);
+    }
+
+    /**
      * Called after the user has succeeded at entering their device or profile challenge password,
      * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}.  This will
      * only be received the first time they succeed after having previously
      * failed.
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use
+     *             {@link #onPasswordSucceeded(Context, Intent, UserHandle)} instead.
      */
+    @Deprecated
     public void onPasswordSucceeded(Context context, Intent intent) {
     }
 
     /**
+     * Called after the user has succeeded at entering their device or profile challenge password,
+     * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}.  This will
+     * only be received the first time they succeed after having previously
+     * failed.
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param user The user of profile for whom the password check succeeded.  To see whether this
+     *        user is the current profile or a parent user, check for equality with
+     *        {@link Process#myUserHandle}.
+     */
+    public void onPasswordSucceeded(Context context, Intent intent, UserHandle user) {
+        onPasswordSucceeded(context, intent);
+    }
+
+    /**
      * Called periodically when the device or profile challenge password is about to expire
      * or has expired.  It will typically be called at these times: on device boot, once per day
      * before the password expires, and at the time when the password expires.
@@ -540,11 +598,40 @@
      *
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use
+     *             {@link #onPasswordExpiring(Context, Intent, UserHandle)} instead.
      */
+    @Deprecated
     public void onPasswordExpiring(Context context, Intent intent) {
     }
 
     /**
+     * Called periodically when the device or profile challenge password is about to expire
+     * or has expired.  It will typically be called at these times: on device boot, once per day
+     * before the password expires, and at the time when the password expires.
+     *
+     * <p>If the password is not updated by the user, this method will continue to be called
+     * once per day until the password is changed or the device admin disables password expiration.
+     *
+     * <p>The admin will typically post a notification requesting the user to change their password
+     * in response to this call. The actual password expiration time can be obtained by calling
+     * {@link DevicePolicyManager#getPasswordExpiration(ComponentName) }
+     *
+     * <p>The admin should be sure to take down any notifications it posted in response to this call
+     * when it receives {@link DeviceAdminReceiver#onPasswordChanged(Context, Intent, UserHandle) }.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param user The user or profile for whom the password is expiring. To see whether this
+     *        user is the current profile or a parent user, check for equality with
+     *        {@link Process#myUserHandle}.
+     */
+    public void onPasswordExpiring(Context context, Intent intent, UserHandle user) {
+        onPasswordExpiring(context, intent);
+    }
+
+    /**
      * Called when provisioning of a managed profile or managed device has completed successfully.
      *
      * <p> As a prerequisite for the execution of this callback the {@link DeviceAdminReceiver} has
@@ -741,11 +828,11 @@
         String action = intent.getAction();
 
         if (ACTION_PASSWORD_CHANGED.equals(action)) {
-            onPasswordChanged(context, intent);
+            onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
         } else if (ACTION_PASSWORD_FAILED.equals(action)) {
-            onPasswordFailed(context, intent);
+            onPasswordFailed(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
         } else if (ACTION_PASSWORD_SUCCEEDED.equals(action)) {
-            onPasswordSucceeded(context, intent);
+            onPasswordSucceeded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
         } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) {
             onEnabled(context, intent);
         } else if (ACTION_DEVICE_ADMIN_DISABLE_REQUESTED.equals(action)) {
@@ -757,7 +844,7 @@
         } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) {
             onDisabled(context, intent);
         } else if (ACTION_PASSWORD_EXPIRING.equals(action)) {
-            onPasswordExpiring(context, intent);
+            onPasswordExpiring(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
         } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) {
             onProfileProvisioningComplete(context, intent);
         } else if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) {
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: