Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 10c3ac0d authored by junseok01.lee's avatar junseok01.lee Committed by BK Choi
Browse files

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
parent 51ff3146
Loading
Loading
Loading
Loading
+252 −38
Original line number Diff line number Diff line
@@ -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,12 +394,31 @@ abstract public class ManagedServices {
        }

        synchronized (mMutex) {
            pw.println("    All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
            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 : mEnabledServicesForCurrentProfiles) {
                for (ComponentName cmpt : enabledServicesForCurrentProfiles) {
                    if (filter != null && !filter.matches(cmpt)) continue;
                    pw.println("      " + cmpt);
                }
            }

            pw.println("    Live " + getCaption() + "s (" + mServices.size() + "):");
            for (ManagedServiceInfo info : mServices) {
@@ -442,12 +472,25 @@ abstract public class ManagedServices {
            }
        }


        synchronized (mMutex) {
            for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
            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;
                info.dumpDebug(proto, ManagedServicesProto.LIVE_SERVICES, this);
@@ -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 (!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<>());
@@ -1860,6 +2007,29 @@ abstract public class ManagedServices {
        return true;
    }

    /**
     * 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.
@@ -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);
        }
    }

+30 −16

File changed.

Preview size limit exceeded, changes collapsed.

+7 −0
Original line number Diff line number Diff line
@@ -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"
}
+1 −0
Original line number Diff line number Diff line
@@ -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",
+4 −0
Original line number Diff line number Diff line
@@ -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(
Loading