diff options
| author | 2025-02-10 14:28:42 -0800 | |
|---|---|---|
| committer | 2025-02-11 11:38:12 -0800 | |
| commit | 10c3ac0ddb748ff312ea9299f4b482e461c33218 (patch) | |
| tree | 29b8533e852b5faf57eb2aceb11c4cf292f62b36 | |
| parent | 51ff3146b7ad5b3dc22eee8cc9bed959b3a8df80 (diff) | |
Modify to ensure that ManagedServices supports concurrent multi-user environments
secondary_user_on_secondary_display is for background users that have
access to UI on assigned displays (a.k.a. visible background users) on
devices that have config_multiuserVisibleBackgroundUsers enabled.
The main use case is Automotive's multi-display Whole Cabin experience
where passengers (modeled as visible background users) can interact
with the display in front of them concurrently with the driver
(modeled as the the current user) interacting with driver's display.
Fixes include
- Add new APIs and implement some new logic for operation in a concurrent multi-user environment in Managed Services
- Modify some related logic in ConditionProviders and NotificationManagerService
- Add the DisableFlags annotation for FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER to the unit tests that use legacy code
Bug: 380297485
Flag: com.android.server.notification.managed_services_concurrent_multiuser
Test: manual
atest FrameworksUiServicesTests
atest CtsLegacyNotification28TestCases --user-type secondary_user_on_secondary_display
atest CtsLegacyNotification29TestCases --user-type secondary_user_on_secondary_display
atest CtsNotificationTestCases --user-type secondary_user_on_secondary_display
atest CtsJobSchedulerTestCases --user-type secondary_user_on_secondary_display
atest CtsAppTestCases --user-type secondary_user_on_secondary_display
(cherry picked from https://partner-android-review.googlesource.com/q/commit:6b16d795638c4d8d71dc353e17ba53395bbdfefe)
Change-Id: I43de6f294e451ddc6621db3d4dcf283513b053f2
7 files changed, 946 insertions, 60 deletions
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index b0ef80793cd7..9ed9b6e56f13 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -25,6 +25,8 @@ import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; +import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER; +import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser; import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; import android.annotation.FlaggedApi; @@ -75,7 +77,9 @@ import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerService.DumpFilter; +import com.android.server.pm.UserManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; import org.xmlpull.v1.XmlPullParser; @@ -134,6 +138,7 @@ abstract public class ManagedServices { private final UserProfiles mUserProfiles; protected final IPackageManager mPm; protected final UserManager mUm; + protected final UserManagerInternal mUmInternal; private final Config mConfig; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -157,12 +162,17 @@ abstract public class ManagedServices { protected final ArraySet<String> mDefaultPackages = new ArraySet<>(); // lists the component names of all enabled (and therefore potentially connected) - // app services for current profiles. + // app services for each user. This is intended to support a concurrent multi-user environment. + // key value is the resolved userId. @GuardedBy("mMutex") - private final ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<>(); - // Just the packages from mEnabledServicesForCurrentProfiles + private final SparseArray<ArraySet<ComponentName>> mEnabledServicesByUser = + new SparseArray<>(); + // Just the packages from mEnabledServicesByUser + // This is intended to support a concurrent multi-user environment. + // key value is the resolved userId. @GuardedBy("mMutex") - private final ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>(); + private final SparseArray<ArraySet<String>> mEnabledServicesPackageNamesByUser = + new SparseArray<>(); // Per user id, list of enabled packages that have nevertheless asked not to be run @GuardedBy("mSnoozing") private final SparseSetArray<ComponentName> mSnoozing = new SparseSetArray<>(); @@ -195,6 +205,7 @@ abstract public class ManagedServices { mConfig = getConfig(); mApprovalLevel = APPROVAL_BY_COMPONENT; mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mUmInternal = LocalServices.getService(UserManagerInternal.class); } abstract protected Config getConfig(); @@ -383,11 +394,30 @@ abstract public class ManagedServices { } synchronized (mMutex) { - pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size() - + ") enabled for current profiles:"); - for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { - if (filter != null && !filter.matches(cmpt)) continue; - pw.println(" " + cmpt); + if (managedServicesConcurrentMultiuser()) { + for (int i = 0; i < mEnabledServicesByUser.size(); i++) { + final int userId = mEnabledServicesByUser.keyAt(i); + final ArraySet<ComponentName> componentNames = + mEnabledServicesByUser.get(userId); + String userString = userId == UserHandle.USER_CURRENT + ? "current profiles" : "user " + Integer.toString(userId); + pw.println(" All " + getCaption() + "s (" + componentNames.size() + + ") enabled for " + userString + ":"); + for (ComponentName cmpt : componentNames) { + if (filter != null && !filter.matches(cmpt)) continue; + pw.println(" " + cmpt); + } + } + } else { + final ArraySet<ComponentName> enabledServicesForCurrentProfiles = + mEnabledServicesByUser.get(UserHandle.USER_CURRENT); + pw.println(" All " + getCaption() + "s (" + + enabledServicesForCurrentProfiles.size() + + ") enabled for current profiles:"); + for (ComponentName cmpt : enabledServicesForCurrentProfiles) { + if (filter != null && !filter.matches(cmpt)) continue; + pw.println(" " + cmpt); + } } pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); @@ -442,11 +472,24 @@ abstract public class ManagedServices { } } - synchronized (mMutex) { - for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { - if (filter != null && !filter.matches(cmpt)) continue; - cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + if (managedServicesConcurrentMultiuser()) { + for (int i = 0; i < mEnabledServicesByUser.size(); i++) { + final int userId = mEnabledServicesByUser.keyAt(i); + final ArraySet<ComponentName> componentNames = + mEnabledServicesByUser.get(userId); + for (ComponentName cmpt : componentNames) { + if (filter != null && !filter.matches(cmpt)) continue; + cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + } + } + } else { + final ArraySet<ComponentName> enabledServicesForCurrentProfiles = + mEnabledServicesByUser.get(UserHandle.USER_CURRENT); + for (ComponentName cmpt : enabledServicesForCurrentProfiles) { + if (filter != null && !filter.matches(cmpt)) continue; + cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + } } for (ManagedServiceInfo info : mServices) { if (filter != null && !filter.matches(info.component)) continue; @@ -841,9 +884,31 @@ abstract public class ManagedServices { } } + /** convenience method for looking in mEnabledServicesPackageNamesByUser + * for UserHandle.USER_CURRENT. + * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes + * trunk stable, this API should be deprecated. Additionally, when this method + * is deprecated, the unit tests written using this method should also be revised. + * + * @param pkg target package name + * @return boolean value that indicates whether it is enabled for the current profiles + */ protected boolean isComponentEnabledForPackage(String pkg) { + return isComponentEnabledForPackage(pkg, UserHandle.USER_CURRENT); + } + + /** convenience method for looking in mEnabledServicesPackageNamesByUser + * + * @param pkg target package name + * @param userId the id of the target user + * @return boolean value that indicates whether it is enabled for the target user + */ + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + protected boolean isComponentEnabledForPackage(String pkg, int userId) { synchronized (mMutex) { - return mEnabledServicesPackageNames.contains(pkg); + ArraySet<String> enabledServicesPackageNames = + mEnabledServicesPackageNamesByUser.get(resolveUserId(userId)); + return enabledServicesPackageNames != null && enabledServicesPackageNames.contains(pkg); } } @@ -1016,9 +1081,14 @@ abstract public class ManagedServices { public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) { if (DEBUG) { synchronized (mMutex) { + int resolvedUserId = (managedServicesConcurrentMultiuser() + && (uidList != null && uidList.length > 0)) + ? resolveUserId(UserHandle.getUserId(uidList[0])) + : UserHandle.USER_CURRENT; Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) - + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); + + " mEnabledServicesPackageNames=" + + mEnabledServicesPackageNamesByUser.get(resolvedUserId)); } } @@ -1034,11 +1104,18 @@ abstract public class ManagedServices { } } for (String pkgName : pkgList) { - if (isComponentEnabledForPackage(pkgName)) { - anyServicesInvolved = true; + if (!managedServicesConcurrentMultiuser()) { + if (isComponentEnabledForPackage(pkgName)) { + anyServicesInvolved = true; + } } if (uidList != null && uidList.length > 0) { for (int uid : uidList) { + if (managedServicesConcurrentMultiuser()) { + if (isComponentEnabledForPackage(pkgName, UserHandle.getUserId(uid))) { + anyServicesInvolved = true; + } + } if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) { anyServicesInvolved = true; trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid)); @@ -1065,6 +1142,36 @@ abstract public class ManagedServices { unbindUserServices(user); } + /** + * Call this method when a user is stopped + * + * @param user the id of the stopped user + */ + public void onUserStopped(int user) { + if (!managedServicesConcurrentMultiuser()) { + return; + } + boolean hasAny = false; + synchronized (mMutex) { + if (mEnabledServicesByUser.contains(user) + && mEnabledServicesPackageNamesByUser.contains(user)) { + // Through the ManagedServices.resolveUserId, + // we resolve UserHandle.USER_CURRENT as the key for users + // other than the visible background user. + // Therefore, the user IDs that exist as keys for each member variable + // correspond to the visible background user. + // We need to unbind services of the stopped visible background user. + mEnabledServicesByUser.remove(user); + mEnabledServicesPackageNamesByUser.remove(user); + hasAny = true; + } + } + if (hasAny) { + Slog.i(TAG, "Removing approved services for stopped user " + user); + unbindUserServices(user); + } + } + public void onUserSwitched(int user) { if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user); unbindOtherUserServices(user); @@ -1386,19 +1493,42 @@ abstract public class ManagedServices { protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind, final IntArray activeUsers, SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) { - mEnabledServicesForCurrentProfiles.clear(); - mEnabledServicesPackageNames.clear(); final int nUserIds = activeUsers.size(); - + if (managedServicesConcurrentMultiuser()) { + for (int i = 0; i < nUserIds; ++i) { + final int resolvedUserId = resolveUserId(activeUsers.get(i)); + if (mEnabledServicesByUser.get(resolvedUserId) != null) { + mEnabledServicesByUser.get(resolvedUserId).clear(); + } + if (mEnabledServicesPackageNamesByUser.get(resolvedUserId) != null) { + mEnabledServicesPackageNamesByUser.get(resolvedUserId).clear(); + } + } + } else { + mEnabledServicesByUser.clear(); + mEnabledServicesPackageNamesByUser.clear(); + } for (int i = 0; i < nUserIds; ++i) { - // decode the list of components final int userId = activeUsers.get(i); + // decode the list of components final ArraySet<ComponentName> userComponents = approvedComponentsByUser.get(userId); if (null == userComponents) { componentsToBind.put(userId, new ArraySet<>()); continue; } + final int resolvedUserId = managedServicesConcurrentMultiuser() + ? resolveUserId(userId) + : UserHandle.USER_CURRENT; + ArraySet<ComponentName> enabledServices = + mEnabledServicesByUser.contains(resolvedUserId) + ? mEnabledServicesByUser.get(resolvedUserId) + : new ArraySet<>(); + ArraySet<String> enabledServicesPackageName = + mEnabledServicesPackageNamesByUser.contains(resolvedUserId) + ? mEnabledServicesPackageNamesByUser.get(resolvedUserId) + : new ArraySet<>(); + final Set<ComponentName> add = new HashSet<>(userComponents); synchronized (mSnoozing) { ArraySet<ComponentName> snoozed = mSnoozing.get(userId); @@ -1409,12 +1539,12 @@ abstract public class ManagedServices { componentsToBind.put(userId, add); - mEnabledServicesForCurrentProfiles.addAll(userComponents); - + enabledServices.addAll(userComponents); for (int j = 0; j < userComponents.size(); j++) { - final ComponentName component = userComponents.valueAt(j); - mEnabledServicesPackageNames.add(component.getPackageName()); + enabledServicesPackageName.add(userComponents.valueAt(j).getPackageName()); } + mEnabledServicesByUser.put(resolvedUserId, enabledServices); + mEnabledServicesPackageNamesByUser.put(resolvedUserId, enabledServicesPackageName); } } @@ -1453,13 +1583,9 @@ abstract public class ManagedServices { */ protected void rebindServices(boolean forceRebind, int userToRebind) { if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind); - IntArray userIds = mUserProfiles.getCurrentProfileIds(); boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext) && allowRebindForParentUser(); - if (userToRebind != USER_ALL && !rebindAllCurrentUsers) { - userIds = new IntArray(1); - userIds.add(userToRebind); - } + IntArray userIds = getUserIdsForRebindServices(userToRebind, rebindAllCurrentUsers); final SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>(); @@ -1483,6 +1609,23 @@ abstract public class ManagedServices { bindToServices(componentsToBind); } + private IntArray getUserIdsForRebindServices(int userToRebind, boolean rebindAllCurrentUsers) { + IntArray userIds = mUserProfiles.getCurrentProfileIds(); + if (userToRebind != USER_ALL && !rebindAllCurrentUsers) { + userIds = new IntArray(1); + userIds.add(userToRebind); + } else if (managedServicesConcurrentMultiuser() + && userToRebind == USER_ALL) { + for (UserInfo user : mUm.getUsers()) { + if (mUmInternal.isVisibleBackgroundFullUser(user.id) + && !userIds.contains(user.id)) { + userIds.add(user.id); + } + } + } + return userIds; + } + /** * Called when user switched to unbind all services from other users. */ @@ -1506,7 +1649,11 @@ abstract public class ManagedServices { synchronized (mMutex) { final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices(); for (ManagedServiceInfo info : removableBoundServices) { - if ((allExceptUser && (info.userid != user)) + // User switching is the event for the forground user. + // It should not affect the service of the visible background user. + if ((allExceptUser && (info.userid != user) + && !(managedServicesConcurrentMultiuser() + && info.isVisibleBackgroundUserService)) || (!allExceptUser && (info.userid == user))) { Set<ComponentName> toUnbind = componentsToUnbind.get(info.userid, new ArraySet<>()); @@ -1861,6 +2008,29 @@ abstract public class ManagedServices { } /** + * This method returns the mapped id for the incoming user id + * If the incoming id was not the id of the visible background user, it returns USER_CURRENT. + * In the other cases, it returns the same value as the input. + * + * @param userId the id of the user + * @return the user id if it is a visible background user, otherwise + * {@link UserHandle#USER_CURRENT} + */ + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + @VisibleForTesting + public int resolveUserId(int userId) { + if (managedServicesConcurrentMultiuser()) { + if (mUmInternal.isVisibleBackgroundFullUser(userId)) { + // The dataset of the visible background user should be managed independently. + return userId; + } + } + // The data of current user and its profile users need to be managed + // in a dataset as before. + return UserHandle.USER_CURRENT; + } + + /** * Returns true if services in the parent user should be rebound * when rebindServices is called with a profile userId. * Must be false for NotificationAssistants. @@ -1878,6 +2048,8 @@ abstract public class ManagedServices { public int targetSdkVersion; public Pair<ComponentName, Integer> mKey; public int uid; + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public boolean isVisibleBackgroundUserService; public ManagedServiceInfo(IInterface service, ComponentName component, int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion, @@ -1889,6 +2061,10 @@ abstract public class ManagedServices { this.connection = connection; this.targetSdkVersion = targetSdkVersion; this.uid = uid; + if (managedServicesConcurrentMultiuser()) { + this.isVisibleBackgroundUserService = LocalServices + .getService(UserManagerInternal.class).isVisibleBackgroundFullUser(userid); + } mKey = Pair.create(component, userid); } @@ -1937,19 +2113,28 @@ abstract public class ManagedServices { } public boolean isSameUser(int userId) { - if (!isEnabledForCurrentProfiles()) { + if (!isEnabledForUser()) { return false; } return userId == USER_ALL || userId == this.userid; } public boolean enabledAndUserMatches(int nid) { - if (!isEnabledForCurrentProfiles()) { + if (!isEnabledForUser()) { return false; } if (this.userid == USER_ALL) return true; if (this.isSystem) return true; if (nid == USER_ALL || nid == this.userid) return true; + if (managedServicesConcurrentMultiuser() + && mUmInternal.getProfileParentId(nid) + != mUmInternal.getProfileParentId(this.userid)) { + // If the profile parent IDs do not match each other, + // it is determined that the users do not match. + // This situation may occur when comparing the current user's ID + // with the visible background user's ID. + return false; + } return supportsProfiles() && mUserProfiles.isCurrentProfile(nid) && isPermittedForProfile(nid); @@ -1969,12 +2154,21 @@ abstract public class ManagedServices { removeServiceImpl(this.service, this.userid); } - /** convenience method for looking in mEnabledServicesForCurrentProfiles */ - public boolean isEnabledForCurrentProfiles() { + /** + * convenience method for looking in mEnabledServicesByUser. + * If FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER is disabled, this manages the data using + * only UserHandle.USER_CURRENT as the key, in order to behave the same as the legacy logic. + */ + public boolean isEnabledForUser() { if (this.isSystem) return true; if (this.connection == null) return false; synchronized (mMutex) { - return mEnabledServicesForCurrentProfiles.contains(this.component); + int resolvedUserId = managedServicesConcurrentMultiuser() + ? resolveUserId(this.userid) + : UserHandle.USER_CURRENT; + ArraySet<ComponentName> enabledServices = + mEnabledServicesByUser.get(resolvedUserId); + return enabledServices != null && enabledServices.contains(this.component); } } @@ -2017,10 +2211,30 @@ abstract public class ManagedServices { } } - /** convenience method for looking in mEnabledServicesForCurrentProfiles */ + /** convenience method for looking in mEnabledServicesByUser for UserHandle.USER_CURRENT. + * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes + * trunk stable, this API should be deprecated. Additionally, when this method + * is deprecated, the unit tests written using this method should also be revised. + * + * @param component target component name + * @return boolean value that indicates whether it is enabled for the current profiles + */ public boolean isComponentEnabledForCurrentProfiles(ComponentName component) { + return isComponentEnabledForUser(component, UserHandle.USER_CURRENT); + } + + /** convenience method for looking in mEnabledServicesForUser + * + * @param component target component name + * @param userId the id of the target user + * @return boolean value that indicates whether it is enabled for the target user + */ + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public boolean isComponentEnabledForUser(ComponentName component, int userId) { synchronized (mMutex) { - return mEnabledServicesForCurrentProfiles.contains(component); + ArraySet<ComponentName> enabledServicesForUser = + mEnabledServicesByUser.get(resolveUserId(userId)); + return enabledServicesForUser != null && enabledServicesForUser.contains(component); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3a3deb00562e..8242eeb7a3fe 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -173,6 +173,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.expireBitmaps; +import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; import static com.android.server.utils.PriorityDump.PRIORITY_ARG; @@ -2323,6 +2324,9 @@ public class NotificationManagerService extends SystemService { if (userHandle >= 0) { cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle, REASON_USER_STOPPED); + mConditionProviders.onUserStopped(userHandle); + mListeners.onUserStopped(userHandle); + mAssistants.onUserStopped(userHandle); } } else if ( isProfileUnavailable(action)) { @@ -5715,12 +5719,13 @@ public class NotificationManagerService extends SystemService { public void requestBindListener(ComponentName component) { checkCallerIsSystemOrSameApp(component.getPackageName()); int uid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(uid); final long identity = Binder.clearCallingIdentity(); try { - ManagedServices manager = - mAssistants.isComponentEnabledForCurrentProfiles(component) - ? mAssistants - : mListeners; + boolean isAssistantEnabled = managedServicesConcurrentMultiuser() + ? mAssistants.isComponentEnabledForUser(component, userId) + : mAssistants.isComponentEnabledForCurrentProfiles(component); + ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners; manager.setComponentState(component, UserHandle.getUserId(uid), true); } finally { Binder.restoreCallingIdentity(identity); @@ -5747,16 +5752,16 @@ public class NotificationManagerService extends SystemService { public void requestUnbindListenerComponent(ComponentName component) { checkCallerIsSameApp(component.getPackageName()); int uid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(uid); final long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationLock) { - ManagedServices manager = - mAssistants.isComponentEnabledForCurrentProfiles(component) - ? mAssistants - : mListeners; - if (manager.isPackageOrComponentAllowed(component.flattenToString(), - UserHandle.getUserId(uid))) { - manager.setComponentState(component, UserHandle.getUserId(uid), false); + boolean isAssistantEnabled = managedServicesConcurrentMultiuser() + ? mAssistants.isComponentEnabledForUser(component, userId) + : mAssistants.isComponentEnabledForCurrentProfiles(component); + ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners; + if (manager.isPackageOrComponentAllowed(component.flattenToString(), userId)) { + manager.setComponentState(component, userId, false); } } } finally { @@ -6534,6 +6539,13 @@ public class NotificationManagerService extends SystemService { } catch (NameNotFoundException e) { return false; } + if (managedServicesConcurrentMultiuser()) { + return checkPackagePolicyAccess(pkg) + || mListeners.isComponentEnabledForPackage(pkg, + UserHandle.getCallingUserId()) + || (mDpm != null + && (mDpm.isActiveProfileOwner(uid) || mDpm.isActiveDeviceOwner(uid))); + } //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. return checkPackagePolicyAccess(pkg) || mListeners.isComponentEnabledForPackage(pkg) @@ -6938,7 +6950,8 @@ public class NotificationManagerService extends SystemService { android.Manifest.permission.INTERACT_ACROSS_USERS, "setNotificationListenerAccessGrantedForUser for user " + userId); } - if (mUmInternal.isVisibleBackgroundFullUser(userId)) { + if (!managedServicesConcurrentMultiuser() + && mUmInternal.isVisibleBackgroundFullUser(userId)) { // The main use case for visible background users is the Automotive multi-display // configuration where a passenger can use a secondary display while the driver is // using the main display. NotificationListeners is designed only for the current @@ -13150,7 +13163,8 @@ public class NotificationManagerService extends SystemService { @Override public void onUserUnlocked(int user) { - if (mUmInternal.isVisibleBackgroundFullUser(user)) { + if (!managedServicesConcurrentMultiuser() + && mUmInternal.isVisibleBackgroundFullUser(user)) { // The main use case for visible background users is the Automotive // multi-display configuration where a passenger can use a secondary // display while the driver is using the main display. @@ -13790,7 +13804,7 @@ public class NotificationManagerService extends SystemService { // TODO (b/73052211): if the ranking update changed the notification type, // cancel notifications for NLSes that can't see them anymore for (final ManagedServiceInfo serviceInfo : getServices()) { - if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener( + if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener( serviceInfo, ActivityManager.getCurrentUser())) { continue; } @@ -13818,7 +13832,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") public void notifyListenerHintsChangedLocked(final int hints) { for (final ManagedServiceInfo serviceInfo : getServices()) { - if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener( + if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener( serviceInfo, ActivityManager.getCurrentUser())) { continue; } @@ -13874,7 +13888,7 @@ public class NotificationManagerService extends SystemService { public void notifyInterruptionFilterChanged(final int interruptionFilter) { for (final ManagedServiceInfo serviceInfo : getServices()) { - if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener( + if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener( serviceInfo, ActivityManager.getCurrentUser())) { continue; } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 048f2b6b0cbc..76cd5c88b388 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -210,3 +210,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "managed_services_concurrent_multiuser" + namespace: "systemui" + description: "Enables ManagedServices to support Concurrent multi user environment" + bug: "380297485" +} diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 0eb20eb22380..66d7611a29c6 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -32,6 +32,7 @@ android_test { "androidx.test.rules", "hamcrest-library", "mockito-target-inline-minus-junit4", + "mockito-target-extended", "platform-compat-test-rules", "platform-test-annotations", "platformprotosnano", diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java index b3ec2153542a..c9d5241c57b7 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java +++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java @@ -30,6 +30,7 @@ import android.testing.TestableContext; import androidx.test.InstrumentationRegistry; +import com.android.server.pm.UserManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import org.junit.After; @@ -41,6 +42,7 @@ import org.mockito.MockitoAnnotations; public class UiServiceTestCase { @Mock protected PackageManagerInternal mPmi; + @Mock protected UserManagerInternal mUmi; @Mock protected UriGrantsManagerInternal mUgmInternal; protected static final String PKG_N_MR1 = "com.example.n_mr1"; @@ -92,6 +94,8 @@ public class UiServiceTestCase { } }); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUmi); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); when(mUgmInternal.checkGrantUriPermission( diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index e5c42082ab97..98440ecdad82 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -17,12 +17,17 @@ package com.android.server.notification; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR; +import static android.os.UserHandle.USER_ALL; +import static android.os.UserHandle.USER_CURRENT; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER; +import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser; import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT; import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE; import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; @@ -66,7 +71,9 @@ import android.os.IInterface; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; @@ -83,6 +90,7 @@ import com.android.server.UiServiceTestCase; import com.google.android.collect.Lists; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -105,6 +113,9 @@ import java.util.concurrent.CountDownLatch; public class ManagedServicesTest extends UiServiceTestCase { + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private IPackageManager mIpm; @Mock @@ -155,6 +166,7 @@ public class ManagedServicesTest extends UiServiceTestCase { users.add(new UserInfo(11, "11", 0)); users.add(new UserInfo(12, "12", 0)); users.add(new UserInfo(13, "13", 0)); + users.add(new UserInfo(99, "99", 0)); for (UserInfo user : users) { when(mUm.getUserInfo(eq(user.id))).thenReturn(user); } @@ -804,6 +816,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception { // If the primary and secondary lists contain component names, only those components within // the package should be matched @@ -841,6 +854,45 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void rebindServices_onlyBindsExactMatchesIfComponent_concurrent_multiUser() + throws Exception { + // If the primary and secondary lists contain component names, only those components within + // the package should be matched + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, + ManagedServices.APPROVAL_BY_COMPONENT); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + packages.add("anotherPackage"); + addExpectedServices(service, packages, 0); + + // only 2 components are approved per package + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2"); + + loadXml(service); + // verify the 2 components per package are enabled (bound) + verifyExpectedBoundEntries(service, true, 0); + verifyExpectedBoundEntries(service, false, 0); + + // verify the last component per package is not enabled/we don't try to bind to it + for (String pkg : packages) { + ComponentName unapprovedAdditionalComponent = + ComponentName.unflattenFromString(pkg + "/C3"); + assertFalse( + service.isComponentEnabledForUser( + unapprovedAdditionalComponent, 0)); + verify(mIpm, never()).getServiceInfo( + eq(unapprovedAdditionalComponent), anyLong(), anyInt()); + } + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void rebindServices_bindsEverythingInAPackage() throws Exception { // If the primary and secondary lists contain packages, all components within those packages // should be bound @@ -866,6 +918,32 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void rebindServices_bindsEverythingInAPackage_concurrent_multiUser() throws Exception { + // If the primary and secondary lists contain packages, all components within those packages + // should be bound + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_PACKAGE); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + packages.add("packagea"); + addExpectedServices(service, packages, 0); + + // 2 approved packages + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryPackages.put(0, "package"); + mExpectedSecondaryPackages.clear(); + mExpectedSecondaryPackages.put(0, "packagea"); + + loadXml(service); + + // verify the 3 components per package are enabled (bound) + verifyExpectedBoundEntries(service, true, 0); + verifyExpectedBoundEntries(service, false, 0); + } + + @Test public void reregisterService_checksAppIsApproved_pkg() throws Exception { Context context = mock(Context.class); PackageManager pm = mock(PackageManager.class); @@ -1118,6 +1196,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void testUpgradeAppBindsNewServices() throws Exception { // If the primary and secondary lists contain component names, only those components within // the package should be matched @@ -1159,6 +1238,49 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUpgradeAppBindsNewServices_concurrent_multiUser() throws Exception { + // If the primary and secondary lists contain component names, only those components within + // the package should be matched + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, + ManagedServices.APPROVAL_BY_PACKAGE); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + addExpectedServices(service, packages, 0); + + // only 2 components are approved per package + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryPackages.clear(); + + loadXml(service); + + // new component expected + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2:package/C3"); + + service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); + + // verify the 3 components per package are enabled (bound) + verifyExpectedBoundEntries(service, true, 0); + + // verify the last component per package is not enabled/we don't try to bind to it + for (String pkg : packages) { + ComponentName unapprovedAdditionalComponent = + ComponentName.unflattenFromString(pkg + "/C3"); + assertFalse( + service.isComponentEnabledForUser( + unapprovedAdditionalComponent, 0)); + verify(mIpm, never()).getServiceInfo( + eq(unapprovedAdditionalComponent), anyLong(), anyInt()); + } + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void testUpgradeAppNoPermissionNoRebind() throws Exception { Context context = spy(getContext()); doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); @@ -1211,6 +1333,59 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUpgradeAppNoPermissionNoRebind_concurrent_multiUser() throws Exception { + Context context = spy(getContext()); + doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); + + ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, + mIpm, + APPROVAL_BY_COMPONENT); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + addExpectedServices(service, packages, 0); + + final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1"); + final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2"); + + // Both components are approved initially + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryPackages.clear(); + + loadXml(service); + + //Component package/C1 loses bind permission + when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( + (Answer<ServiceInfo>) invocation -> { + ComponentName invocationCn = invocation.getArgument(0); + if (invocationCn != null) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = invocationCn.getPackageName(); + serviceInfo.name = invocationCn.getClassName(); + if (invocationCn.equals(unapprovedComponent)) { + serviceInfo.permission = "none"; + } else { + serviceInfo.permission = service.getConfig().bindPermission; + } + serviceInfo.metaData = null; + return serviceInfo; + } + return null; + } + ); + + // Trigger package update + service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); + + assertFalse(service.isComponentEnabledForUser(unapprovedComponent, 0)); + assertTrue(service.isComponentEnabledForUser(approvedComponent, 0)); + } + + @Test public void testSetPackageOrComponentEnabled() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1517,6 +1692,201 @@ public class ManagedServicesTest extends UiServiceTestCase { assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c"))); } + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testPopulateComponentsToBindWithNonProfileUser() { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + + SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>(); + ArraySet<ComponentName> allowed0 = new ArraySet<>(); + allowed0.add(ComponentName.unflattenFromString("a/a")); + approvedComponentsByUser.put(0, allowed0); + ArraySet<ComponentName> allowed10 = new ArraySet<>(); + allowed10.add(ComponentName.unflattenFromString("b/b")); + approvedComponentsByUser.put(10, allowed10); + + int nonProfileUser = 99; + ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>(); + allowedForNonProfileUser.add(ComponentName.unflattenFromString("c/c")); + approvedComponentsByUser.put(nonProfileUser, allowedForNonProfileUser); + + IntArray users = new IntArray(); + users.add(nonProfileUser); + users.add(10); + users.add(0); + + SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(nonProfileUser)).thenReturn(true); + + service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser); + + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("a/a"), 0)); + assertTrue(service.isComponentEnabledForPackage("a", 0)); + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("b/b"), 10)); + assertTrue(service.isComponentEnabledForPackage("b", 0)); + assertTrue(service.isComponentEnabledForPackage("b", 10)); + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("c/c"), nonProfileUser)); + assertTrue(service.isComponentEnabledForPackage("c", nonProfileUser)); + } + + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testRebindService_profileUser() throws Exception { + final int profileUserId = 10; + when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); + spyOn(mService); + ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass( + IntArray.class); + when(mService.allowRebindForParentUser()).thenReturn(true); + + mService.rebindServices(false, profileUserId); + + verify(mService).populateComponentsToBind(any(), captor.capture(), any()); + assertTrue(captor.getValue().contains(0)); + assertTrue(captor.getValue().contains(profileUserId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testRebindService_nonProfileUser() throws Exception { + final int userId = 99; + when(mUserProfiles.isProfileUser(userId, mContext)).thenReturn(false); + spyOn(mService); + ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass( + IntArray.class); + when(mService.allowRebindForParentUser()).thenReturn(true); + + mService.rebindServices(false, userId); + + verify(mService).populateComponentsToBind(any(), captor.capture(), any()); + assertFalse(captor.getValue().contains(0)); + assertTrue(captor.getValue().contains(userId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testRebindService_userAll() throws Exception { + final int userId = 99; + spyOn(mService); + spyOn(mService.mUmInternal); + when(mService.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true); + ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass( + IntArray.class); + when(mService.allowRebindForParentUser()).thenReturn(true); + + mService.rebindServices(false, USER_ALL); + + verify(mService).populateComponentsToBind(any(), captor.capture(), any()); + assertTrue(captor.getValue().contains(0)); + assertTrue(captor.getValue().contains(userId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testOnUserStoppedWithVisibleBackgroundUser() throws Exception { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + int userId = 99; + SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>(); + ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>(); + allowedForNonProfileUser.add(ComponentName.unflattenFromString("a/a")); + approvedComponentsByUser.put(userId, allowedForNonProfileUser); + IntArray users = new IntArray(); + users.add(userId); + SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true); + service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser); + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("a/a"), userId)); + assertTrue(service.isComponentEnabledForPackage("a", userId)); + + service.onUserStopped(userId); + + assertFalse(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("a/a"), userId)); + assertFalse(service.isComponentEnabledForPackage("a", userId)); + verify(service).unbindUserServices(eq(userId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUnbindServicesImpl_serviceOfForegroundUser() throws Exception { + int switchingUserId = 10; + int userId = 99; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(false); + + IInterface iInterface = mock(IInterface.class); + when(iInterface.asBinder()).thenReturn(mock(IBinder.class)); + + ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo( + iInterface, ComponentName.unflattenFromString("a/a"), userId, false, + mock(ServiceConnection.class), 26, 34); + + Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>(); + removableBoundServices.add(serviceInfo); + + when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices); + ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass( + SparseArray.class); + + service.unbindServicesImpl(switchingUserId, true); + + verify(service).unbindFromServices(captor.capture()); + + assertEquals(captor.getValue().size(), 1); + assertTrue(captor.getValue().indexOfKey(userId) != -1); + assertTrue(captor.getValue().get(userId).contains( + ComponentName.unflattenFromString("a/a"))); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUnbindServicesImpl_serviceOfVisibleBackgroundUser() throws Exception { + int switchingUserId = 10; + int userId = 99; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true); + + IInterface iInterface = mock(IInterface.class); + when(iInterface.asBinder()).thenReturn(mock(IBinder.class)); + + ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo( + iInterface, ComponentName.unflattenFromString("a/a"), userId, + false, mock(ServiceConnection.class), 26, 34); + + Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>(); + removableBoundServices.add(serviceInfo); + + when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices); + ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass( + SparseArray.class); + + service.unbindServicesImpl(switchingUserId, true); + + verify(service).unbindFromServices(captor.capture()); + + assertEquals(captor.getValue().size(), 0); + } + @Test public void testOnNullBinding() throws Exception { Context context = mock(Context.class); @@ -1681,6 +2051,7 @@ public class ManagedServicesTest extends UiServiceTestCase { assertFalse(service.isBound(cn, mZero.id)); assertFalse(service.isBound(cn, mTen.id)); } + @Test public void testOnPackagesChanged_nullValuesPassed_noNullPointers() { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { @@ -2012,6 +2383,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isComponentEnabledForCurrentProfiles_isThreadSafe() throws InterruptedException { for (UserInfo userInfo : mUm.getUsers()) { mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true); @@ -2024,6 +2396,20 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isComponentEnabledForUser_isThreadSafe() throws InterruptedException { + for (UserInfo userInfo : mUm.getUsers()) { + mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true); + } + testThreadSafety(() -> { + mService.rebindServices(false, 0); + assertThat(mService.isComponentEnabledForUser( + new ComponentName("pkg1", "cmp1"), 0)).isTrue(); + }, 20, 30); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isComponentEnabledForCurrentProfiles_profileUserId() { final int profileUserId = 10; when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); @@ -2037,6 +2423,24 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isComponentEnabledForUser_profileUserId() { + final int profileUserId = 10; + when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); + spyOn(mService); + doReturn(USER_CURRENT).when(mService).resolveUserId(anyInt()); + + // Only approve for parent user (0) + mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true); + + // Test that the component is enabled after calling rebindServices with profile userId (10) + mService.rebindServices(false, profileUserId); + assertThat(mService.isComponentEnabledForUser( + new ComponentName("pkg1", "cmp1"), profileUserId)).isTrue(); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() { final int profileUserId = 10; when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); @@ -2054,6 +2458,25 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isComponentEnabledForUser_profileUserId_NAS() { + final int profileUserId = 10; + when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); + // Do not rebind for parent users (NAS use-case) + ManagedServices service = spy(mService); + when(service.allowRebindForParentUser()).thenReturn(false); + doReturn(USER_CURRENT).when(service).resolveUserId(anyInt()); + + // Only approve for parent user (0) + service.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true); + + // Test that the component is disabled after calling rebindServices with profile userId (10) + service.rebindServices(false, profileUserId); + assertThat(service.isComponentEnabledForUser( + new ComponentName("pkg1", "cmp1"), profileUserId)).isFalse(); + } + + @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) public void testManagedServiceInfoIsSystemUi() { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, @@ -2069,6 +2492,48 @@ public class ManagedServicesTest extends UiServiceTestCase { assertThat(service0.isSystemUi()).isFalse(); } + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUserMatchesAndEnabled_profileUser() throws Exception { + int currentUserId = 10; + int profileUserId = 11; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo( + mock(IInterface.class), ComponentName.unflattenFromString("a/a"), currentUserId, + false, mock(ServiceConnection.class), 26, 34)); + + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId); + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId); + doReturn(true).when(listener).isEnabledForUser(); + doReturn(true).when(mUserProfiles).isCurrentProfile(anyInt()); + + assertThat(listener.enabledAndUserMatches(profileUserId)).isTrue(); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUserMatchesAndDisabled_visibleBackgroudUser() throws Exception { + int currentUserId = 10; + int profileUserId = 11; + int visibleBackgroundUserId = 12; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo( + mock(IInterface.class), ComponentName.unflattenFromString("a/a"), profileUserId, + false, mock(ServiceConnection.class), 26, 34)); + + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId); + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId); + doReturn(visibleBackgroundUserId).when(service.mUmInternal) + .getProfileParentId(visibleBackgroundUserId); + doReturn(true).when(listener).isEnabledForUser(); + + assertThat(listener.enabledAndUserMatches(visibleBackgroundUserId)).isFalse(); + } + private void mockServiceInfoWithMetaData(List<ComponentName> componentNames, ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException { @@ -2247,26 +2712,47 @@ public class ManagedServicesTest extends UiServiceTestCase { private void verifyExpectedBoundEntries(ManagedServices service, boolean primary) throws Exception { + verifyExpectedBoundEntries(service, primary, UserHandle.USER_CURRENT); + } + + private void verifyExpectedBoundEntries(ManagedServices service, boolean primary, + int targetUserId) throws Exception { ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel) : mExpectedSecondary.get(service.mApprovalLevel); for (int userId : verifyMap.keySet()) { for (String packageOrComponent : verifyMap.get(userId).split(":")) { if (!TextUtils.isEmpty(packageOrComponent)) { if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) { - assertTrue(packageOrComponent, - service.isComponentEnabledForPackage(packageOrComponent)); + if (managedServicesConcurrentMultiuser()) { + assertTrue(packageOrComponent, + service.isComponentEnabledForPackage(packageOrComponent, + targetUserId)); + } else { + assertTrue(packageOrComponent, + service.isComponentEnabledForPackage(packageOrComponent)); + } for (int i = 1; i <= 3; i++) { ComponentName componentName = ComponentName.unflattenFromString( packageOrComponent +"/C" + i); - assertTrue(service.isComponentEnabledForCurrentProfiles( - componentName)); + if (managedServicesConcurrentMultiuser()) { + assertTrue(service.isComponentEnabledForUser( + componentName, targetUserId)); + } else { + assertTrue(service.isComponentEnabledForCurrentProfiles( + componentName)); + } verify(mIpm, times(1)).getServiceInfo( eq(componentName), anyLong(), anyInt()); } } else { ComponentName componentName = ComponentName.unflattenFromString(packageOrComponent); - assertTrue(service.isComponentEnabledForCurrentProfiles(componentName)); + if (managedServicesConcurrentMultiuser()) { + assertTrue(service.isComponentEnabledForUser(componentName, + targetUserId)); + } else { + assertTrue(service.isComponentEnabledForCurrentProfiles(componentName)); + } verify(mIpm, times(1)).getServiceInfo( eq(componentName), anyLong(), anyInt()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 0373eb6e9318..3ec41fd673b1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -140,6 +140,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; +import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER; import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS; import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY; import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION; @@ -867,7 +868,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); } - if (filter.hasAction(Intent.ACTION_USER_SWITCHED) + if (filter.hasAction(Intent.ACTION_USER_STOPPED) + || filter.hasAction(Intent.ACTION_USER_SWITCHED) || filter.hasAction(Intent.ACTION_PROFILE_UNAVAILABLE) || filter.hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { // There may be multiple receivers, get the NMS one @@ -16287,6 +16289,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void onUserStopped_callBackToListeners() { + Intent intent = new Intent(Intent.ACTION_USER_STOPPED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, 20); + + mUserIntentReceiver.onReceive(mContext, intent); + + verify(mConditionProviders).onUserStopped(eq(20)); + verify(mListeners).onUserStopped(eq(20)); + verify(mAssistants).onUserStopped(eq(20)); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_invalidPackage() throws Exception { final String notReal = "NOT REAL"; final var checker = mService.permissionChecker; @@ -16303,6 +16319,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_invalidPackage_concurrent_multiUser() + throws Exception { + final String notReal = "NOT REAL"; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow( + PackageManager.NameNotFoundException.class); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(notReal)).isFalse(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(notReal), anyInt()); + verify(checker, never()).check(any(), anyInt(), anyInt(), anyBoolean()); + verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(notReal), anyInt()); + verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt()); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_hasPermission() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16321,6 +16356,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_hasPermission_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(checker.check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt()); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_isPackageAllowed() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16339,6 +16395,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_isPackageAllowed_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mConditionProviders.isPackageOrComponentAllowed(eq(packageName), anyInt())) + .thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt()); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_isComponentEnabled() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16356,6 +16433,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_isComponentEnabled_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mListeners.isComponentEnabledForPackage(packageName, mUserId)).thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_isDeviceOwner() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16372,10 +16469,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mDevicePolicyManager).isActiveDeviceOwner(uid); } + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_isDeviceOwner_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mDevicePolicyManager.isActiveDeviceOwner(uid)).thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager).isActiveDeviceOwner(uid); + } + /** * b/292163859 */ @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_callerIsDeviceOwner() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16394,7 +16511,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid); } + /** + * b/292163859 + */ + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_callerIsDeviceOwner_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final int callingUid = Binder.getCallingUid(); + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mDevicePolicyManager.isActiveDeviceOwner(callingUid)).thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager).isActiveDeviceOwner(uid); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid); + } + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_notGranted() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16411,6 +16553,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_notGranted_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager).isActiveDeviceOwner(uid); + } + + @Test public void testResetDefaultDnd() { TestableNotificationManagerService service = spy(mService); UserInfo user = new UserInfo(0, "owner", 0); |