Loading core/java/android/content/pm/multiuser.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -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" Loading services/core/java/com/android/server/am/UserController.java +44 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -2846,6 +2848,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; } Loading @@ -2866,6 +2878,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; } Loading Loading @@ -3599,6 +3617,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); Loading Loading @@ -4535,5 +4556,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; } } } services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +27 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -1020,6 +1021,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. Loading Loading
core/java/android/content/pm/multiuser.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
services/core/java/com/android/server/am/UserController.java +44 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -2846,6 +2848,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; } Loading @@ -2866,6 +2878,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; } Loading Loading @@ -3599,6 +3617,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); Loading Loading @@ -4535,5 +4556,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; } } }
services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +27 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -1020,6 +1021,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. Loading