diff options
7 files changed, 949 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..62e26e189a35 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,10 @@ abstract public class ManagedServices { mConfig = getConfig(); mApprovalLevel = APPROVAL_BY_COMPONENT; mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mUmInternal = LocalServices.getService(UserManagerInternal.class); + // Initialize for the current user. + mEnabledServicesByUser.put(UserHandle.USER_CURRENT, new ArraySet<>()); + mEnabledServicesPackageNamesByUser.put(UserHandle.USER_CURRENT, new ArraySet<>()); } abstract protected Config getConfig(); @@ -383,11 +397,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 +475,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 +887,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 +1084,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 +1107,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 +1145,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 +1496,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.get(UserHandle.USER_CURRENT).clear(); + mEnabledServicesPackageNamesByUser.get(UserHandle.USER_CURRENT).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 +1542,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 +1586,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 +1612,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 +1652,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 +2011,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 +2051,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 +2064,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 +2116,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 +2157,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 +2214,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 340afb776405..09c8b5ba823e 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)) { @@ -5730,12 +5734,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); @@ -5762,16 +5767,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 { @@ -6549,6 +6554,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) @@ -6953,7 +6965,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 @@ -13165,7 +13178,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. @@ -13805,7 +13819,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; } @@ -13833,7 +13847,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; } @@ -13889,7 +13903,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 415e3accfa39..858dd3a605d8 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 @@ -16323,6 +16325,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; @@ -16339,6 +16355,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; @@ -16357,6 +16392,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; @@ -16375,6 +16431,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; @@ -16392,6 +16469,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; @@ -16408,10 +16505,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; @@ -16430,7 +16547,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; @@ -16447,6 +16589,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); |