diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index f2a87772cef50..a248bce2a6cab 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -768,6 +768,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Called when a new batch of security logs can be retrieved. * + *

If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if security logging is enabled). + * See {@link DevicePolicyManager#setAffiliationIds} + * *

This callback is only applicable to device owners. * * @param context The running context as per {@link #onReceive}. @@ -782,13 +786,18 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * ever be called when network logging is enabled. The logs can only be retrieved while network * logging is enabled. * + *

If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if network logging is enabled). It will also no longer be + * possible to retrieve the network logs batch with the most recent {@code batchToken} provided + * by this callback. See {@link DevicePolicyManager#setAffiliationIds}. + * *

This callback is only applicable to device owners. * * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. * @param batchToken The token representing the current batch of network logs. * @param networkLogsCount The total count of events in the current batch of network logs. - * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName) + * @see DevicePolicyManager#retrieveNetworkLogs */ public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken, int networkLogsCount) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 074326fd3531f..c95e0113e8012 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3651,15 +3651,16 @@ public class DevicePolicyManager { /** * Called by a device owner to request a bugreport. *

- * There must be only one user on the device, managed by the device owner. Otherwise a - * {@link SecurityException} will be thrown. + * If the device contains secondary users or profiles, they must be affiliated with the device + * owner user. Otherwise a {@link SecurityException} will be thrown. See + * {@link #setAffiliationIds}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return {@code true} if the bugreport collection started successfully, or {@code false} if it * wasn't triggered because a previous bugreport operation is still active (either the * bugreport is still running or waiting for the user to share or decline) - * @throws SecurityException if {@code admin} is not a device owner, or if there are users other - * than the one managed by the device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. */ public boolean requestBugreport(@NonNull ComponentName admin) { throwIfParentInstance("requestBugreport"); @@ -6631,14 +6632,16 @@ public class DevicePolicyManager { } /** - * Called by device owner to control the security logging feature. Logging can only be - * enabled on single user devices where the sole user is managed by the device owner. + * Called by device owner to control the security logging feature. * *

Security logs contain various information intended for security auditing purposes. * See {@link SecurityEvent} for details. * - *

There must be only one user on the device, managed by the device owner. - * Otherwise a {@link SecurityException} will be thrown. + *

Note: The device owner won't be able to retrieve security logs if there + * are unaffiliated secondary users or profiles on the device, regardless of whether the + * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for + * all users to become affiliated. Therefore it's recommended that affiliation ids are set for + * new users as soon as possible after provisioning via {@link #setAffiliationIds}. * * @param admin Which device owner this request is associated with. * @param enabled whether security logging should be enabled or not. @@ -6680,13 +6683,16 @@ public class DevicePolicyManager { *

Access to the logs is rate limited and it will only return new logs after the device * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}. * - *

There must be only one user on the device, managed by the device owner. - * Otherwise a {@link SecurityException} will be thrown. + *

If there is any other user or profile on the device, it must be affiliated with the + * device owner. Otherwise a {@link SecurityException} will be thrown. See + * {@link #setAffiliationIds} * * @param admin Which device owner this request is associated with. * @return the new batch of security logs which is a list of {@link SecurityEvent}, * or {@code null} if rate limitation is exceeded or if logging is currently disabled. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. + * @see DeviceAdminReceiver#onSecurityLogsAvailable */ public @Nullable List retrieveSecurityLogs(@NonNull ComponentName admin) { throwIfParentInstance("retrieveSecurityLogs"); @@ -6726,14 +6732,17 @@ public class DevicePolicyManager { * will result in {@code null} being returned. The device logs are retrieved from a RAM region * which is not guaranteed to be corruption-free during power cycles, as a result be cautious * about data corruption when parsing. - *

- * There must be only one user on the device, managed by the device owner. Otherwise a - * {@link SecurityException} will be thrown. + * + *

If there is any other user or profile on the device, it must be affiliated with the + * device owner. Otherwise a {@link SecurityException} will be thrown. See + * {@link #setAffiliationIds} * * @param admin Which device owner this request is associated with. * @return Device logs from before the latest reboot of the system, or {@code null} if this API * is not supported on the device. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. + * @see #retrieveSecurityLogs */ public @Nullable List retrievePreRebootSecurityLogs( @NonNull ComponentName admin) { @@ -6939,6 +6948,12 @@ public class DevicePolicyManager { * Indicates the entity that controls the device or profile owner. Two users/profiles are * affiliated if the set of ids set by their device or profile owners intersect. * + *

Note: Features that depend on user affiliation (such as security logging + * or {@link #bindDeviceAdminServiceAsUser}) won't be available when a secondary user or profile + * is created, until it becomes affiliated. Therefore it is recommended that the appropriate + * affiliation ids are set by its profile owner as soon as possible after the user/profile is + * created. + * * @param admin Which profile or device owner this request is associated with. * @param ids A list of opaque non-empty affiliation ids. Duplicate elements will be ignored. * @@ -7138,15 +7153,19 @@ public class DevicePolicyManager { } /** - * Called by a device owner to control the network logging feature. Logging can only be - * enabled on single user devices where the sole user is managed by the device owner. If a new - * user is added on the device, logging is disabled. + * Called by a device owner to control the network logging feature. * *

Network logs contain DNS lookup and connect() library call events. * + *

Note: The device owner won't be able to retrieve network logs if there + * are unaffiliated secondary users or profiles on the device, regardless of whether the + * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for + * all users to become affiliated. Therefore it's recommended that affiliation ids are set for + * new users as soon as possible after provisioning via {@link #setAffiliationIds}. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled whether network logging should be enabled or not. - * @throws {@link SecurityException} if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner. * @see #retrieveNetworkLogs */ public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { @@ -7164,7 +7183,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only * be {@code null} if the caller has MANAGE_USERS permission. * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. - * @throws {@link SecurityException} if {@code admin} is not a device owner and caller has + * @throws SecurityException if {@code admin} is not a device owner and caller has * no MANAGE_USERS permission */ public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { @@ -7190,12 +7209,19 @@ public class DevicePolicyManager { * after the device device owner has been notified via * {@link DeviceAdminReceiver#onNetworkLogsAvailable}. * + *

If a secondary user or profile is created, calling this method will throw a + * {@link SecurityException} until all users become affiliated again. It will also no longer be + * possible to retrieve the network logs batch with the most recent batchToken provided + * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See + * {@link DevicePolicyManager#setAffiliationIds}. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param batchToken A token of the batch to retrieve * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns * {@code null} if the batch represented by batchToken is no longer available or if * logging is disabled. - * @throws {@link SecurityException} if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. * @see DeviceAdminReceiver#onNetworkLogsAvailable */ public @Nullable List retrieveNetworkLogs(@NonNull ComponentName admin, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index bdd1a0f5100a7..040188dded2cd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,7 +17,6 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; -import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; @@ -49,7 +48,6 @@ import android.Manifest.permission; import android.accessibilityservice.AccessibilityServiceInfo; import android.accounts.Account; import android.accounts.AccountManager; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -180,8 +178,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -337,7 +333,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h /** - * Strings logged with {@link #PROVISIONING_ENTRY_POINT_ADB}. + * Strings logged with {@link + * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}. */ private static final String LOG_TAG_PROFILE_OWNER = "profile-owner"; private static final String LOG_TAG_DEVICE_OWNER = "device-owner"; @@ -552,11 +549,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (Intent.ACTION_USER_ADDED.equals(action)) { sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); + synchronized (DevicePolicyManagerService.this) { + // It might take a while for the user to become affiliated. Make security + // and network logging unavailable in the meantime. + maybePauseDeviceWideLoggingLocked(); + } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_REMOVED, userHandle); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); - removeUserData(userHandle); + synchronized (DevicePolicyManagerService.this) { + // Check whether the user is affiliated, *before* removing its data. + boolean isRemovedUserAffiliated = isUserAffiliatedWithDeviceLocked(userHandle); + removeUserData(userHandle); + if (!isRemovedUserAffiliated) { + // We discard the logs when unaffiliated users are deleted (so that the + // device owner cannot retrieve data about that user after it's gone). + discardDeviceWideLogsLocked(); + // Resume logging if all remaining users are affiliated. + maybeResumeDeviceWideLoggingLocked(); + } + } } else if (Intent.ACTION_USER_STARTED.equals(action)) { synchronized (DevicePolicyManagerService.this) { // Reset the policy data @@ -1858,9 +1869,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mOwners.hasDeviceOwner()) { mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true"); Slog.i(LOG_TAG, "Set ro.device_owner property to true"); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); + if (mInjector.securityLogGetLoggingEnabledProperty()) { mSecurityLogMonitor.start(); + maybePauseDeviceWideLoggingLocked(); } } else { mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "false"); @@ -3155,7 +3167,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // It's temporary solution to clear DISALLOW_ADD_USER after CTS - // TODO: b/31952368 when the restriction is moved from system to the device owner, + // STOPSHIP(b/31952368) when the restriction is moved from system to the device owner, // it can be removed. private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) { if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { @@ -5650,34 +5662,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private boolean isDeviceOwnerManagedSingleUserDevice() { - synchronized (this) { - if (!mOwners.hasDeviceOwner()) { - return false; - } - } - final long callingIdentity = mInjector.binderClearCallingIdentity(); - try { - if (mInjector.userManagerIsSplitSystemUser()) { - // In split system user mode, only allow the case where the device owner is managing - // the only non-system user of the device - return (mUserManager.getUserCount() == 2 - && mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM); - } else { - return mUserManager.getUserCount() == 1; - } - } finally { - mInjector.binderRestoreCallingIdentity(callingIdentity); - } - } - - private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException { + private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException { synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - } - if (!isDeviceOwnerManagedSingleUserDevice()) { - throw new SecurityException( - "There should only be one user, managed by Device Owner"); + if (!areAllUsersAffiliatedWithDeviceLocked()) { + throw new SecurityException("Not all users are affiliated."); + } } } @@ -5687,7 +5677,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } Preconditions.checkNotNull(who, "ComponentName is null"); - ensureDeviceOwnerManagingSingleUser(who); + + // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport + // which could still contain data related to that user. Should we disallow that, e.g. until + // next boot? Might not be needed given that this still requires user consent. + ensureDeviceOwnerAndAllUsersAffiliated(who); if (mRemoteBugreportServiceIsActive.get() || (getDeviceOwnerRemoteBugreportUri() != null)) { @@ -5712,7 +5706,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mRemoteBugreportServiceIsActive.set(true); mRemoteBugreportSharingAccepted.set(false); registerRemoteBugreportReceivers(); - mInjector.getNotificationManager().notifyAsUser(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID, + mInjector.getNotificationManager().notifyAsUser(LOG_TAG, + RemoteBugreportUtils.NOTIFICATION_ID, RemoteBugreportUtils.buildNotification(mContext, DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL); mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, @@ -6259,6 +6254,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin.userRestrictions = null; admin.defaultEnabledRestrictionsAlreadySet.clear(); admin.forceEphemeralUsers = false; + admin.isNetworkLoggingEnabled = false; mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers); final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); policyData.mLastSecurityLogRetrievalTime = -1; @@ -6271,7 +6267,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.clearDeviceOwner(); mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); + + mInjector.securityLogSetLoggingEnabledProperty(false); + mSecurityLogMonitor.stop(); + setNetworkLoggingActiveInternal(false); + try { if (mInjector.getIBackupManager() != null) { // Reactivate backup service. @@ -8261,7 +8261,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); final int userHandle = mInjector.userHandleGetCallingUserId(); - if (isUserAffiliatedWithDevice(userHandle)) { + if (isUserAffiliatedWithDeviceLocked(userHandle)) { setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages))); } else { throw new SecurityException("Admin " + who + @@ -9442,6 +9442,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { getUserData(UserHandle.USER_SYSTEM).mAffiliationIds = affiliationIds; saveSettingsLocked(UserHandle.USER_SYSTEM); } + + // Affiliation status for any user, not just the calling user, might have changed. + // The device owner user will still be affiliated after changing its affiliation ids, + // but as a result of that other users might become affiliated or un-affiliated. + maybePauseDeviceWideLoggingLocked(); + maybeResumeDeviceWideLoggingLocked(); } } @@ -9461,84 +9467,78 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isAffiliatedUser() { - return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId()); + if (!mHasFeature) { + return false; + } + + synchronized (this) { + return isUserAffiliatedWithDeviceLocked(mInjector.userHandleGetCallingUserId()); + } } - private boolean isUserAffiliatedWithDevice(int userId) { - synchronized (this) { - if (!mOwners.hasDeviceOwner()) { - return false; - } - if (userId == mOwners.getDeviceOwnerUserId()) { - // The user that the DO is installed on is always affiliated with the device. + private boolean isUserAffiliatedWithDeviceLocked(int userId) { + if (!mOwners.hasDeviceOwner()) { + return false; + } + if (userId == mOwners.getDeviceOwnerUserId()) { + // The user that the DO is installed on is always affiliated with the device. + return true; + } + if (userId == UserHandle.USER_SYSTEM) { + // The system user is always affiliated in a DO device, even if the DO is set on a + // different user. This could be the case if the DO is set in the primary user + // of a split user device. + return true; + } + final ComponentName profileOwner = getProfileOwner(userId); + if (profileOwner == null) { + return false; + } + final Set userAffiliationIds = getUserData(userId).mAffiliationIds; + final Set deviceAffiliationIds = + getUserData(UserHandle.USER_SYSTEM).mAffiliationIds; + for (String id : userAffiliationIds) { + if (deviceAffiliationIds.contains(id)) { return true; } - if (userId == UserHandle.USER_SYSTEM) { - // The system user is always affiliated in a DO device, even if the DO is set on a - // different user. This could be the case if the DO is set in the primary user - // of a split user device. - return true; - } - final ComponentName profileOwner = getProfileOwner(userId); - if (profileOwner == null) { - return false; - } - final Set userAffiliationIds = getUserData(userId).mAffiliationIds; - final Set deviceAffiliationIds = - getUserData(UserHandle.USER_SYSTEM).mAffiliationIds; - for (String id : userAffiliationIds) { - if (deviceAffiliationIds.contains(id)) { - return true; - } - } } return false; } - private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() { - final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice(); - - // disable security logging if needed - if (!isSingleUserManagedDevice) { - mInjector.securityLogSetLoggingEnabledProperty(false); - Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed" - + " device."); - } - - // disable backup service if needed - // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by - // the device owner - if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) { - setBackupServiceEnabledInternal(false); - Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user."); - } - - // disable network logging if needed - if (!isSingleUserManagedDevice) { - setNetworkLoggingActiveInternal(false); - Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed" - + " device."); - // if there still is a device owner, disable logging policy, otherwise the admin - // has been nuked - if (mOwners.hasDeviceOwner()) { - getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false; - saveSettingsLocked(mOwners.getDeviceOwnerUserId()); + private boolean areAllUsersAffiliatedWithDeviceLocked() { + final long ident = mInjector.binderClearCallingIdentity(); + try { + final List userInfos = mUserManager.getUsers(); + for (int i = 0; i < userInfos.size(); i++) { + int userId = userInfos.get(i).id; + if (!isUserAffiliatedWithDeviceLocked(userId)) { + Slog.d(LOG_TAG, "User id " + userId + " not affiliated."); + return false; + } } + } finally { + mInjector.binderRestoreCallingIdentity(ident); } + + return true; } @Override public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) { + if (!mHasFeature) { + return; + } Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) { return; } mInjector.securityLogSetLoggingEnabledProperty(enabled); if (enabled) { mSecurityLogMonitor.start(); + maybePauseDeviceWideLoggingLocked(); } else { mSecurityLogMonitor.stop(); } @@ -9547,6 +9547,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isSecurityLoggingEnabled(ComponentName admin) { + if (!mHasFeature) { + return false; + } + Preconditions.checkNotNull(admin); synchronized (this) { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); @@ -9565,10 +9569,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public ParceledListSlice retrievePreRebootSecurityLogs(ComponentName admin) { - Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + if (!mHasFeature) { + return null; + } - if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs)) { + Preconditions.checkNotNull(admin); + ensureDeviceOwnerAndAllUsersAffiliated(admin); + + if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs) + || !mInjector.securityLogGetLoggingEnabledProperty()) { return null; } @@ -9586,8 +9595,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public ParceledListSlice retrieveSecurityLogs(ComponentName admin) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + ensureDeviceOwnerAndAllUsersAffiliated(admin); + + if (!mInjector.securityLogGetLoggingEnabledProperty()) { + return null; + } recordSecurityLogRetrievalTime(); @@ -9794,18 +9811,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + // TODO(b/22388012): When backup is available for secondary users and profiles, consider + // whether there are any privacy/security implications of enabling the backup service here + // if there are other users or profiles unmanaged or managed by a different entity (i.e. not + // affiliated). @Override public void setBackupServiceEnabled(ComponentName admin, boolean enabled) { - Preconditions.checkNotNull(admin); if (!mHasFeature) { return; } - ensureDeviceOwnerManagingSingleUser(admin); - setBackupServiceEnabledInternal(enabled); - } + Preconditions.checkNotNull(admin); + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } - private synchronized void setBackupServiceEnabledInternal(boolean enabled) { - long ident = mInjector.binderClearCallingIdentity(); + final long ident = mInjector.binderClearCallingIdentity(); try { IBackupManager ibm = mInjector.getIBackupManager(); if (ibm != null) { @@ -9906,7 +9926,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner); final boolean isCallerManagedProfile = isManagedProfile(callingUserId); if ((!isCallerDeviceOwner && !isCallerManagedProfile) - || !isUserAffiliatedWithDevice(callingUserId)) { + || !isUserAffiliatedWithDeviceLocked(callingUserId)) { return targetUsers; } @@ -9926,7 +9946,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Both must be the same package and be affiliated in order to bind. if (callingOwnerPackage.equals(targetOwnerPackage) - && isUserAffiliatedWithDevice(userId)) { + && isUserAffiliatedWithDeviceLocked(userId)) { targetUsers.add(UserHandle.of(userId)); } } @@ -10024,7 +10044,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (enabled == isNetworkLoggingEnabledInternalLocked()) { // already in the requested state @@ -10051,10 +10071,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging" + " service not being available yet."); } + maybePauseDeviceWideLoggingLocked(); sendNetworkLoggingNotificationLocked(); } else { if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) { - mNetworkLogger = null; Slog.wtf(LOG_TAG, "Network logging could not be stopped due to the logging" + " service not being available yet."); } @@ -10066,6 +10086,44 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + /** Pauses security and network logging if there are unaffiliated users on the device */ + private void maybePauseDeviceWideLoggingLocked() { + if (!areAllUsersAffiliatedWithDeviceLocked()) { + Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be " + + "paused if enabled."); + mSecurityLogMonitor.pause(); + if (mNetworkLogger != null) { + mNetworkLogger.pause(); + } + } + } + + /** Resumes security and network logging (if they are enabled) if all users are affiliated */ + private void maybeResumeDeviceWideLoggingLocked() { + if (areAllUsersAffiliatedWithDeviceLocked()) { + final long ident = mInjector.binderClearCallingIdentity(); + try { + mSecurityLogMonitor.resume(); + if (mNetworkLogger != null) { + mNetworkLogger.resume(); + } + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + } + + /** Deletes any security and network logs that might have been collected so far */ + private void discardDeviceWideLogsLocked() { + mSecurityLogMonitor.discardLogs(); + if (mNetworkLogger != null) { + mNetworkLogger.discardLogs(); + } + // TODO: We should discard pre-boot security logs here too, as otherwise those + // logs (which might contain data from the user just removed) will be + // available after next boot. + } + @Override public boolean isNetworkLoggingEnabled(ComponentName admin) { if (!mHasFeature) { @@ -10090,32 +10148,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH */ @Override - public synchronized List retrieveNetworkLogs(ComponentName admin, - long batchToken) { + public List retrieveNetworkLogs(ComponentName admin, long batchToken) { if (!mHasFeature) { return null; } Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + ensureDeviceOwnerAndAllUsersAffiliated(admin); - if (mNetworkLogger == null) { - return null; - } - - if (!isNetworkLoggingEnabledInternalLocked()) { - return null; - } - - final long currentTime = System.currentTimeMillis(); synchronized (this) { + if (mNetworkLogger == null + || !isNetworkLoggingEnabledInternalLocked()) { + return null; + } + + final long currentTime = System.currentTimeMillis(); DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); if (currentTime > policyData.mLastNetworkLogsRetrievalTime) { policyData.mLastNetworkLogsRetrievalTime = currentTime; saveSettingsLocked(UserHandle.USER_SYSTEM); } + return mNetworkLogger.retrieveLogs(batchToken); } - - return mNetworkLogger.retrieveLogs(batchToken); } private void sendNetworkLoggingNotificationLocked() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index b82cb3cfbeaff..00859311af847 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -31,7 +31,6 @@ import android.util.Slog; import com.android.server.ServiceThread; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -130,6 +129,8 @@ final class NetworkLogger { Log.d(TAG, "Stopping network logging"); // stop the logging regardless of whether we fail to unregister listener mIsLoggingEnabled.set(false); + discardLogs(); + try { if (!checkIpConnectivityMetricsService()) { // the IIpConnectivityMetrics service should have been present at this point @@ -140,9 +141,43 @@ final class NetworkLogger { return mIpConnectivityMetrics.unregisterNetdEventCallback(); } catch (RemoteException re) { Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re); - } finally { - mHandlerThread.quitSafely(); return true; + } finally { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + } + } + } + + /** + * If logs are being collected, keep collecting them but stop notifying the device owner that + * new logs are available (since they cannot be retrieved) + */ + void pause() { + if (mNetworkLoggingHandler != null) { + mNetworkLoggingHandler.pause(); + } + } + + /** + * If logs are being collected, start notifying the device owner when logs are ready to be + * collected again (if it was paused). + *

If logging is enabled and there are logs ready to be retrieved, this method will attempt + * to notify the device owner. Therefore calling identity should be cleared before calling it + * (in case the method is called from a user other than the DO's user). + */ + void resume() { + if (mNetworkLoggingHandler != null) { + mNetworkLoggingHandler.resume(); + } + } + + /** + * Discard all collected logs. + */ + void discardLogs() { + if (mNetworkLoggingHandler != null) { + mNetworkLoggingHandler.discardLogs(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java index baa4c13f51908..7d6841248dcd2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -55,10 +55,16 @@ final class NetworkLoggingHandler extends Handler { @GuardedBy("this") private ArrayList mFullBatch; - // each full batch is represented by its token, which the DPC has to provide back to revieve it + @GuardedBy("this") + private boolean mPaused = false; + + // each full batch is represented by its token, which the DPC has to provide back to retrieve it @GuardedBy("this") private long mCurrentFullBatchToken; + @GuardedBy("this") + private long mLastRetrievedFullBatchToken; + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { super(looper); mDpm = dpm; @@ -70,15 +76,19 @@ final class NetworkLoggingHandler extends Handler { case LOG_NETWORK_EVENT_MSG: { NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); if (networkEvent != null) { - mNetworkEvents.add(networkEvent); - if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) { - finalizeBatchAndNotifyDeviceOwnerIfNotEmpty(); + synchronized (NetworkLoggingHandler.this) { + mNetworkEvents.add(networkEvent); + if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) { + finalizeBatchAndNotifyDeviceOwnerLocked(); + } } } break; } case FINALIZE_BATCH_MSG: { - finalizeBatchAndNotifyDeviceOwnerIfNotEmpty(); + synchronized (NetworkLoggingHandler.this) { + finalizeBatchAndNotifyDeviceOwnerLocked(); + } break; } } @@ -91,22 +101,49 @@ final class NetworkLoggingHandler extends Handler { + "ms from now."); } - private synchronized void finalizeBatchAndNotifyDeviceOwnerIfNotEmpty() { + synchronized void pause() { + Log.d(TAG, "Paused network logging"); + mPaused = true; + } + + synchronized void resume() { + if (!mPaused) { + Log.d(TAG, "Attempted to resume network logging, but logging is not paused."); + return; + } + + Log.d(TAG, "Resumed network logging. Current batch=" + + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken); + mPaused = false; + + // If there is a full batch ready that the device owner hasn't been notified about, do it + // now. + if (mFullBatch != null && mFullBatch.size() > 0 + && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) { + scheduleBatchFinalization(); + notifyDeviceOwnerLocked(); + } + } + + synchronized void discardLogs() { + mFullBatch = null; + mNetworkEvents = new ArrayList(); + Log.d(TAG, "Discarded all network logs"); + } + + @GuardedBy("this") + private void finalizeBatchAndNotifyDeviceOwnerLocked() { if (mNetworkEvents.size() > 0) { // finalize the batch and start a new one from scratch mFullBatch = mNetworkEvents; mCurrentFullBatchToken++; mNetworkEvents = new ArrayList(); - // notify DO that there's a new non-empty batch waiting - Bundle extras = new Bundle(); - extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); - extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); - Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " - + mCurrentFullBatchToken); - mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + if (!mPaused) { + notifyDeviceOwnerLocked(); + } } else { // don't notify the DO, since there are no events; DPC can still retrieve - // the last full batch + // the last full batch if not paused. Log.d(TAG, "Was about to finalize the batch, but there were no events to send to" + " the DPC, the batchToken of last available batch: " + mCurrentFullBatchToken); @@ -115,10 +152,21 @@ final class NetworkLoggingHandler extends Handler { scheduleBatchFinalization(); } + @GuardedBy("this") + private void notifyDeviceOwnerLocked() { + Bundle extras = new Bundle(); + extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); + extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); + Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " + + mCurrentFullBatchToken); + mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + } + synchronized List retrieveFullLogBatch(long batchToken) { if (batchToken != mCurrentFullBatchToken) { return null; } + mLastRetrievedFullBatchToken = mCurrentFullBatchToken; return mFullBatch; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index 79702a8de5051..18f06be063cd9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import android.app.admin.DeviceAdminReceiver; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; +import android.os.SystemClock; import android.util.Log; import android.util.Slog; @@ -50,7 +51,7 @@ class SecurityLogMonitor implements Runnable { mService = service; } - private static final boolean DEBUG = false; + private static final boolean DEBUG = false; // STOPSHIP if true. private static final String TAG = "SecurityLogMonitor"; /** * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N} @@ -78,17 +79,25 @@ class SecurityLogMonitor implements Runnable { private ArrayList mPendingLogs = new ArrayList(); @GuardedBy("mLock") private boolean mAllowedToRetrieve = false; - // When DO will be allowed to retrieves the log, in milliseconds. + + /** + * When DO will be allowed to retrieve the log, in milliseconds since boot (as per + * {@link SystemClock#elapsedRealtime()}) + */ @GuardedBy("mLock") - private long mNextAllowedRetrivalTimeMillis = -1; + private long mNextAllowedRetrievalTimeMillis = -1; + @GuardedBy("mLock") + private boolean mPaused = false; void start() { + Slog.i(TAG, "Starting security logging."); mLock.lock(); try { if (mMonitorThread == null) { mPendingLogs = new ArrayList(); mAllowedToRetrieve = false; - mNextAllowedRetrivalTimeMillis = -1; + mNextAllowedRetrievalTimeMillis = -1; + mPaused = false; mMonitorThread = new Thread(this); mMonitorThread.start(); @@ -99,6 +108,7 @@ class SecurityLogMonitor implements Runnable { } void stop() { + Slog.i(TAG, "Stopping security logging."); mLock.lock(); try { if (mMonitorThread != null) { @@ -111,7 +121,8 @@ class SecurityLogMonitor implements Runnable { // Reset state and clear buffer mPendingLogs = new ArrayList(); mAllowedToRetrieve = false; - mNextAllowedRetrivalTimeMillis = -1; + mNextAllowedRetrievalTimeMillis = -1; + mPaused = false; mMonitorThread = null; } } finally { @@ -119,6 +130,58 @@ class SecurityLogMonitor implements Runnable { } } + /** + * If logs are being collected, keep collecting them but stop notifying the device owner that + * new logs are available (since they cannot be retrieved). + */ + void pause() { + Slog.i(TAG, "Paused."); + + mLock.lock(); + mPaused = true; + mAllowedToRetrieve = false; + mLock.unlock(); + } + + /** + * If logs are being collected, start notifying the device owner when logs are ready to be + * retrieved again (if it was paused). + *

If logging is enabled and there are logs ready to be retrieved, this method will attempt + * to notify the device owner. Therefore calling identity should be cleared before calling it + * (in case the method is called from a user other than the DO's user). + */ + void resume() { + mLock.lock(); + try { + if (!mPaused) { + Log.d(TAG, "Attempted to resume, but logging is not paused."); + return; + } + mPaused = false; + mAllowedToRetrieve = false; + } finally { + mLock.unlock(); + } + + Slog.i(TAG, "Resumed."); + try { + notifyDeviceOwnerIfNeeded(); + } catch (InterruptedException e) { + Log.w(TAG, "Thread interrupted.", e); + } + } + + /** + * Discard all collected logs. + */ + void discardLogs() { + mLock.lock(); + mAllowedToRetrieve = false; + mPendingLogs = new ArrayList(); + mLock.unlock(); + Slog.i(TAG, "Discarded all logs."); + } + /** * Returns the new batch of logs since the last call to this method. Returns null if * rate limit is exceeded. @@ -128,7 +191,7 @@ class SecurityLogMonitor implements Runnable { try { if (mAllowedToRetrieve) { mAllowedToRetrieve = false; - mNextAllowedRetrivalTimeMillis = System.currentTimeMillis() + mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime() + RATE_LIMIT_INTERVAL_MILLISECONDS; List result = mPendingLogs; mPendingLogs = new ArrayList(); @@ -163,7 +226,7 @@ class SecurityLogMonitor implements Runnable { SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs); } if (!logs.isEmpty()) { - if (DEBUG) Slog.d(TAG, "processing new logs"); + if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size()); mLock.lockInterruptibly(); try { mPendingLogs.addAll(logs); @@ -172,6 +235,7 @@ class SecurityLogMonitor implements Runnable { mPendingLogs = new ArrayList(mPendingLogs.subList( mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2), mPendingLogs.size())); + Slog.i(TAG, "Pending logs buffer full. Discarding old logs."); } } finally { mLock.unlock(); @@ -188,7 +252,7 @@ class SecurityLogMonitor implements Runnable { break; } } - if (DEBUG) Slog.d(TAG, "MonitorThread exit."); + Slog.i(TAG, "MonitorThread exit."); } private void notifyDeviceOwnerIfNeeded() throws InterruptedException { @@ -196,15 +260,24 @@ class SecurityLogMonitor implements Runnable { boolean allowToRetrieveNow = false; mLock.lockInterruptibly(); try { + if (mPaused) { + return; + } + + // STOPSHIP(b/34186771): If the previous notification didn't reach the DO and logs were + // not retrieved (e.g. the broadcast was sent before the user was unlocked), no more + // subsequent callbacks will be sent. We should make sure that the DO gets notified + // before logs are lost. int logSize = mPendingLogs.size(); if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) { // Allow DO to retrieve logs if too many pending logs allowToRetrieveNow = true; + if (DEBUG) Slog.d(TAG, "Number of log entries over threshold: " + logSize); } else if (logSize > 0) { - if (mNextAllowedRetrivalTimeMillis == -1 || - System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) { + if (SystemClock.elapsedRealtime() >= mNextAllowedRetrievalTimeMillis) { // Rate limit reset allowToRetrieveNow = true; + if (DEBUG) Slog.d(TAG, "Timeout reached"); } } shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow; @@ -213,7 +286,7 @@ class SecurityLogMonitor implements Runnable { mLock.unlock(); } if (shouldNotifyDO) { - if (DEBUG) Slog.d(TAG, "notify DO"); + Slog.i(TAG, "notify DO"); mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE, null); } 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 e68895e03f9c9..60f436019fa8d 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2845,7 +2845,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testGetLastSecurityLogRetrievalTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); - when(mContext.userManager.getUserCount()).thenReturn(1); + + // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the + // feature is disabled because there are non-affiliated secondary users. + mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE); when(mContext.resources.getBoolean(R.bool.config_supportPreRebootSecurityLogs)) .thenReturn(true); @@ -2854,6 +2857,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Enabling logging should not change the timestamp. dpm.setSecurityLoggingEnabled(admin1, true); + verify(mContext.settings) + .securityLogSetLoggingEnabledProperty(true); + when(mContext.settings.securityLogGetLoggingEnabledProperty()) + .thenReturn(true); assertEquals(-1, dpm.getLastSecurityLogRetrievalTime()); // Retrieving the logs should update the timestamp. @@ -2906,7 +2913,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testGetLastBugReportRequestTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); - when(mContext.userManager.getUserCount()).thenReturn(1); + mContext.packageName = admin1.getPackageName(); mContext.applicationInfo = new ApplicationInfo(); when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject())) @@ -2914,6 +2921,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(mContext.resources.getColor(eq(R.color.notification_material_background_color), anyObject())).thenReturn(Color.WHITE); + // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the + // feature is disabled because there are non-affiliated secondary users. + mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE); + // No bug reports were requested so far. assertEquals(-1, dpm.getLastBugReportRequestTime()); @@ -2951,7 +2962,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testGetLastNetworkLogRetrievalTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); - when(mContext.userManager.getUserCount()).thenReturn(1); + mContext.packageName = admin1.getPackageName(); + mContext.applicationInfo = new ApplicationInfo(); + when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject())) + .thenReturn(Color.WHITE); + when(mContext.resources.getColor(eq(R.color.notification_material_background_color), + anyObject())).thenReturn(Color.WHITE); + + // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the + // feature is disabled because there are non-affiliated secondary users. + mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE); when(mContext.iipConnectivityMetrics.registerNetdEventCallback(anyObject())) .thenReturn(true); 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 a1b676907914d..44bf547460ddf 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -393,6 +393,18 @@ public class DpmMockContext extends MockContext { return dir; } + public void removeUser(int userId) { + for (int i = 0; i < mUserInfos.size(); i++) { + if (mUserInfos.get(i).id == userId) { + mUserInfos.remove(i); + break; + } + } + when(userManager.getUserInfo(eq(userId))).thenReturn(null); + + when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(false); + } + private UserInfo getUserInfo(int userId) { for (UserInfo ui : mUserInfos) { if (ui.id == userId) {