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
This commit is contained in:
Yasin Kilicdere
2022-11-04 12:39:33 +00:00
parent 8c6312f12b
commit 3430907152
3 changed files with 48 additions and 31 deletions

View File

@@ -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";

View File

@@ -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;

View File

@@ -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) {