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

Commit fec7fc4c authored by Adam Bookatz's avatar Adam Bookatz
Browse files

Reschedule bg stop if user has visible activities

If the user is currently displaying a visible activity (e.g. because it
is running an app with the FLAG_SHOW_FOR_ALL_USERS flag), we should not
automatically stop the user (in stopExcessRunningUsers or
processScheduledStopOfBackgroundUser); instead, we reschedule the stop
for later.

Test: atest FrameworksServicesTests:com.android.server.am.UserControllerTest#testScheduleStopOfBackgroundUser_rescheduleIfVisibleActivity
Fixes: 423731424
Flag: android.multiuser.reschedule_stop_if_visible_activities
Change-Id: Ia2a18646d0898f8b1075106d4017620ec1fe8364
parent e3ef8bea
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -298,6 +298,13 @@ flag {
    bug: "419867128"
}

flag {
    name: "reschedule_stop_if_visible_activities"
    namespace: "multiuser"
    description: "Don't automatically stop a background user that has a top visible activity; instead, reschedule it to stop later"
    bug: "423731424"
}

flag {
    name: "stop_previous_user_apps"
    namespace: "multiuser"
+44 −1
Original line number Diff line number Diff line
@@ -150,6 +150,7 @@ import com.android.server.pm.UserManagerInternal.UserStartMode;
import com.android.server.pm.UserManagerService;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityAssistInfo;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;

@@ -621,11 +622,12 @@ class UserController implements Handler.Callback {
        }

        final List<UserInfo> users = mInjector.getUserManager().getUsers(true);
        final ArraySet<Integer> visibleActivityUsers = mInjector.getVisibleActivityUsers();
        for (int i = 0; i < users.size(); i++) {
            final int userId = users.get(i).id;
            if (isUserVisible(userId)) {
                exemptedUsers.add(userId);
            } else if (avoidStoppingUserRightNow(userId)) {
            } else if (avoidStoppingUserRightNow(userId, visibleActivityUsers)) {
                avoidUsers.add(userId);
            }
        }
@@ -2849,6 +2851,16 @@ class UserController implements Handler.Callback {
     * Makes requests of other services, so don't call while holding a lock.
     */
    private boolean avoidStoppingUserRightNow(@UserIdInt int userId) {
        return avoidStoppingUserRightNow(userId, mInjector.getVisibleActivityUsers());
    }

    /**
     * Same as {@link #avoidStoppingUserRightNow(int)} but, for efficiency, takes the output of
     * {@link Injector#getVisibleActivityUsers()} as a parameter.
     */
    private boolean avoidStoppingUserRightNow(
            @UserIdInt int userId, ArraySet<Integer> visibleActivityUsers) {

        if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            return false;
        }
@@ -2869,6 +2881,12 @@ class UserController implements Handler.Callback {
                        userId, relatedUserId);
                return true;
            }
            if (visibleActivityUsers.contains(userId)) {
                // User is displaying the top activity from a currently visible root task.
                Slogf.d(TAG, "Avoid stopping user %d because user %d has a visible activity",
                        userId, relatedUserId);
                return true;
            }
        }
        return false;
    }
@@ -3602,6 +3620,9 @@ class UserController implements Handler.Callback {
    /**
     * Returns whether the user is currently visible, including users visible on a background
     * display and always-visible users (e.g. the communal profile).
     *
     * <p>Note that this is whether the user itself is considered a visible user, not merely a user
     * that happens to be displaying a {@link Injector#getVisibleActivityUsers() visible activity}.
     */
    private boolean isUserVisible(@UserIdInt int userId) {
        return mInjector.getUserManagerInternal().isUserVisible(userId);
@@ -4534,5 +4555,27 @@ class UserController implements Handler.Callback {
                t.traceEnd();
            }
        }

        /**
         * Returns the set of users that are currently displaying visible activities.
         *
         * <p>Even a non-visible background user could be visibly displaying an activity in special
         * circumstances, such as if it is running an app with
         * {@link android.content.pm.ActivityInfo#FLAG_SHOW_FOR_ALL_USERS}.
         */
        ArraySet<Integer> getVisibleActivityUsers() {
            if (!android.multiuser.Flags.rescheduleStopIfVisibleActivities()) {
                return new ArraySet<>();
            }
            ActivityTaskManagerInternal atmi
                    = LocalServices.getService(ActivityTaskManagerInternal.class);
            final ArraySet<Integer> visibleActivityUsers = new ArraySet<>();
            if (atmi != null) {
                for (ActivityAssistInfo info : atmi.getTopVisibleActivities()) {
                    visibleActivityUsers.add(info.getUserId());
                }
            }
            return visibleActivityUsers;
        }
    }
}
+27 −0
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.util.Log;
import android.util.StringBuilderPrinter;
import android.util.TimeUtils;
@@ -1017,6 +1018,32 @@ public class UserControllerTest {
        assertRunningUsersIgnoreOrder(SYSTEM_USER_ID, TEST_USER_ID);
    }

    /** Test scheduling stopping of background users - reschedule if user has visible activity. */
    @Test
    public void testScheduleStopOfBackgroundUser_rescheduleIfVisibleActivity() throws Exception {
        mSetFlagsRule.enableFlags(
                android.multiuser.Flags.FLAG_RESCHEDULE_STOP_IF_VISIBLE_ACTIVITIES,
                android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER,
                android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER_BY_DEFAULT);
        assumeFalse(UserManager.isVisibleBackgroundUsersEnabled());

        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
                /* backgroundUserScheduledStopTimeSecs= */ 2);

        setUpAndStartUserInBackground(TEST_USER_ID);
        setUpAndStartUserInBackground(TEST_USER_ID1);
        assertRunningUsersIgnoreOrder(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1);

        doReturn(new ArraySet(List.of(TEST_USER_ID))).when(mInjector).getVisibleActivityUsers();

        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);

        // TEST_USER_ID1 should be stopped. But TEST_USER_ID shouldn't as it has a visible activity.
        assertRunningUsersIgnoreOrder(SYSTEM_USER_ID, TEST_USER_ID);
    }

    /**
     * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
     * @param userId the user we are checking to see whether it is scheduled.