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

Commit 34309071 authored by Yasin Kilicdere's avatar Yasin Kilicdere
Browse files

Defer sending broadcasts to when the user switch is complete.

Send the following broadcasts after a user switch is complete,
instead of while the switch is happening.
* ACTION_USER_BACKGROUND
* ACTION_USER_FOREGROUND
* ACTION_USER_SWITCHED

Otherwise managing these broadcasts occupy system_server process and
make the user switch longer than it could be.

If a code should be run while the user switch is happening, in other
words while the screen is frozen, (because possibly they move things
around on the screen), they should register a UserSwitchObserver and
override its onUserSwitching method. Then when they are done, they
should call reply.sendResult(null); on the provided reply parameter.

Bug: 257437695
Test: atest FrameworksServicesTests:UserControllerTest
Change-Id: I7b8662600b45459cd83653ac6f15e73335903262
parent 8c6312f1
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -3889,7 +3889,7 @@ public class Intent implements Parcelable, Cloneable {
            "android.intent.action.USER_INITIALIZE";

    /**
     * Sent when a user switch is happening, causing the process's user to be
     * Sent after a user switch is complete, if the switch caused the process's user to be
     * brought to the foreground.  This is only sent to receivers registered
     * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
     * Context.registerReceiver}.  It is sent to the user that is going to the
@@ -3901,7 +3901,7 @@ public class Intent implements Parcelable, Cloneable {
            "android.intent.action.USER_FOREGROUND";

    /**
     * Sent when a user switch is happening, causing the process's user to be
     * Sent after a user switch is complete, if the switch caused the process's user to be
     * sent to the background.  This is only sent to receivers registered
     * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
     * Context.registerReceiver}.  It is sent to the user that is going to the
@@ -4042,6 +4042,11 @@ public class Intent implements Parcelable, Cloneable {
     * the current state of the user.
     * @hide
     */
    /*
     * This broadcast is sent after the user switch is complete. In case a task needs to be done
     * while the switch is happening (i.e. while the screen is frozen to hide UI jank), please use
     * ActivityManagerService.registerUserSwitchObserver method.
     */
    @SystemApi
    public static final String ACTION_USER_SWITCHED =
            "android.intent.action.USER_SWITCHED";
+17 −14
Original line number Diff line number Diff line
@@ -1769,7 +1769,7 @@ class UserController implements Handler.Callback {

            if (foreground) {
                t.traceBegin("moveUserToForeground");
                moveUserToForeground(uss, oldUserId, userId);
                moveUserToForeground(uss, userId);
                t.traceEnd();
            } else {
                t.traceBegin("finishUserBoot");
@@ -1983,21 +1983,25 @@ class UserController implements Handler.Callback {

    /** Called on handler thread */
    @VisibleForTesting
    void dispatchUserSwitchComplete(@UserIdInt int userId) {
    void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("dispatchUserSwitchComplete-" + userId);
        t.traceBegin("dispatchUserSwitchComplete-" + newUserId);
        mInjector.getWindowManager().setSwitchingUser(false);
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        for (int i = 0; i < observerCount; i++) {
            try {
                t.traceBegin("onUserSwitchComplete-" + userId + " #" + i + " "
                t.traceBegin("onUserSwitchComplete-" + newUserId + " #" + i + " "
                        + mUserSwitchObservers.getBroadcastCookie(i));
                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId);
                t.traceEnd();
            } catch (RemoteException e) {
                // Ignore
            }
        }
        mUserSwitchObservers.finishBroadcast();
        t.traceBegin("sendUserSwitchBroadcasts-" + oldUserId + "-" + newUserId);
        sendUserSwitchBroadcasts(oldUserId, newUserId);
        t.traceEnd();
        t.traceEnd();
    }

@@ -2159,7 +2163,8 @@ class UserController implements Handler.Callback {

        // Do the keyguard dismiss and unfreeze later
        mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(COMPLETE_USER_SWITCH_MSG, newUserId, 0));
        mHandler.sendMessage(mHandler.obtainMessage(
                COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId));

        uss.switching = false;
        stopGuestOrEphemeralUserIfBackground(oldUserId);
@@ -2169,7 +2174,7 @@ class UserController implements Handler.Callback {
    }

    @VisibleForTesting
    void completeUserSwitch(int newUserId) {
    void completeUserSwitch(int oldUserId, int newUserId) {
        final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
        final Runnable runnable = () -> {
            if (isUserSwitchUiEnabled) {
@@ -2177,7 +2182,7 @@ class UserController implements Handler.Callback {
            }
            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(
                    REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
                    REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
        };

        // If there is no challenge set, dismiss the keyguard right away
@@ -2202,7 +2207,7 @@ class UserController implements Handler.Callback {
        t.traceEnd();
    }

    private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
    private void moveUserToForeground(UserState uss, int newUserId) {
        boolean homeInFront = mInjector.taskSupervisorSwitchUser(newUserId, uss);
        if (homeInFront) {
            mInjector.startHomeActivity(newUserId, "moveUserToForeground");
@@ -2210,7 +2215,6 @@ class UserController implements Handler.Callback {
            mInjector.taskSupervisorResumeFocusedStackTopActivity();
        }
        EventLogTags.writeAmSwitchUser(newUserId);
        sendUserSwitchBroadcasts(oldUserId, newUserId);
    }

    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
@@ -3080,9 +3084,8 @@ class UserController implements Handler.Callback {
                dispatchForegroundProfileChanged(msg.arg1);
                break;
            case REPORT_USER_SWITCH_COMPLETE_MSG:
                dispatchUserSwitchComplete(msg.arg1);

                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
                dispatchUserSwitchComplete(msg.arg1, msg.arg2);
                logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER,
                        USER_LIFECYCLE_EVENT_STATE_FINISH);
                break;
            case REPORT_LOCKED_BOOT_COMPLETE_MSG:
@@ -3100,7 +3103,7 @@ class UserController implements Handler.Callback {
                logAndClearSessionId(msg.arg1);
                break;
            case COMPLETE_USER_SWITCH_MSG:
                completeUserSwitch(msg.arg1);
                completeUserSwitch(msg.arg1, msg.arg2);
                break;
        }
        return false;
+24 −15
Original line number Diff line number Diff line
@@ -113,6 +113,8 @@ import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Tests for {@link UserController}.
@@ -146,9 +148,11 @@ public class UserControllerTest {

    private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
            Intent.ACTION_USER_STARTED,
            Intent.ACTION_USER_SWITCHED,
            Intent.ACTION_USER_STARTING);

    private static final List<String> START_FOREGROUND_USER_DEFERRED_ACTIONS = newArrayList(
            Intent.ACTION_USER_SWITCHED);

    private static final List<String> START_BACKGROUND_USER_ACTIONS = newArrayList(
            Intent.ACTION_USER_STARTED,
            Intent.ACTION_LOCKED_BOOT_COMPLETED,
@@ -397,11 +401,11 @@ public class UserControllerTest {
    private void continueAndCompleteUserSwitch(UserState userState, int oldUserId, int newUserId) {
        mUserController.continueUserSwitch(userState, oldUserId, newUserId);
        mInjector.mHandler.removeMessages(UserController.COMPLETE_USER_SWITCH_MSG);
        mUserController.completeUserSwitch(newUserId);
        mUserController.completeUserSwitch(oldUserId, newUserId);
    }

    @Test
    public void testContinueUserSwitch() throws RemoteException {
    public void testContinueUserSwitch() {
        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
        // Start user -- this will update state of mUserController
@@ -416,12 +420,12 @@ public class UserControllerTest {
        continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
        verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
        verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
        continueUserSwitchAssertions(TEST_USER_ID, false);
        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
        verifySystemUserVisibilityChangesNeverNotified();
    }

    @Test
    public void testContinueUserSwitchDismissKeyguard() throws RemoteException {
    public void testContinueUserSwitchDismissKeyguard() {
        when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
@@ -437,12 +441,12 @@ public class UserControllerTest {
        continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
        verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
        verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
        continueUserSwitchAssertions(TEST_USER_ID, false);
        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
        verifySystemUserVisibilityChangesNeverNotified();
    }

    @Test
    public void testContinueUserSwitchUIDisabled() throws RemoteException {
    public void testContinueUserSwitchUIDisabled() {
        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);

@@ -457,11 +461,11 @@ public class UserControllerTest {
        // Verify that continueUserSwitch worked as expected
        continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
        verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
        continueUserSwitchAssertions(TEST_USER_ID, false);
        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
    }

    private void continueUserSwitchAssertions(int expectedUserId, boolean backgroundUserStopping)
            throws RemoteException {
    private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId,
            boolean backgroundUserStopping) {
        Set<Integer> expectedCodes = new LinkedHashSet<>();
        expectedCodes.add(COMPLETE_USER_SWITCH_MSG);
        expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -473,7 +477,8 @@ public class UserControllerTest {
        assertEquals("Unexpected message sent", expectedCodes, actualCodes);
        Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
        assertNotNull(msg);
        assertEquals("Unexpected userId", expectedUserId, msg.arg1);
        assertEquals("Unexpected oldUserId", expectedOldUserId, msg.arg1);
        assertEquals("Unexpected newUserId", expectedNewUserId, msg.arg2);
    }

    @Test
@@ -486,16 +491,21 @@ public class UserControllerTest {
        mUserController.startUser(TEST_USER_ID, true);
        Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
        assertNotNull(reportMsg);
        int oldUserId = reportMsg.arg1;
        int newUserId = reportMsg.arg2;
        mInjector.mHandler.clearAllRecordedMessages();
        // Mockito can't reset only interactions, so just verify that this hasn't been
        // called with 'false' until after dispatchUserSwitchComplete.
        verify(mInjector.getWindowManager(), never()).setSwitchingUser(false);
        // Call dispatchUserSwitchComplete
        mUserController.dispatchUserSwitchComplete(newUserId);
        mUserController.dispatchUserSwitchComplete(oldUserId, newUserId);
        verify(observer, times(1)).onUserSwitchComplete(anyInt());
        verify(observer).onUserSwitchComplete(TEST_USER_ID);
        verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false);
        startUserAssertions(Stream.concat(
                        START_FOREGROUND_USER_ACTIONS.stream(),
                        START_FOREGROUND_USER_DEFERRED_ACTIONS.stream()
                ).collect(Collectors.toList()), Collections.emptySet());
    }

    @Test
@@ -902,8 +912,7 @@ public class UserControllerTest {
    }

    private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
            int expectedNumberOfCalls, boolean expectOldUserStopping)
            throws RemoteException {
            int expectedNumberOfCalls, boolean expectOldUserStopping) {
        // Start user -- this will update state of mUserController
        mUserController.startUser(newUserId, true);
        Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -918,7 +927,7 @@ public class UserControllerTest {
        continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
        verify(mInjector.getWindowManager(), times(expectedNumberOfCalls))
                .stopFreezingScreen();
        continueUserSwitchAssertions(newUserId, expectOldUserStopping);
        continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
    }

    private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {