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:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user