[User Switcher] Add user and guest on bg thread.

Adds user and switches to user on bg thread. Pixel Launcher keeps
stopping dialog shows, but this is from a Log.wtf that is in launcher
code.

Bug: 234546491
Test: Manual on device
Change-Id: Idfc45e7983fabbc4668de7cb9fd8149fdce20e6c
This commit is contained in:
Aaron Liu
2022-07-18 11:44:34 -07:00
parent 0b0822b3c5
commit ac8d93bfd3
5 changed files with 189 additions and 94 deletions

View File

@@ -62,7 +62,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -936,15 +935,17 @@ public class UserSwitcherController implements Dumpable {
guestCreationProgressDialog.show();
// userManager.createGuest will block the thread so post is needed for the dialog to show
ThreadUtils.postOnMainThread(() -> {
mBgExecutor.execute(() -> {
final int guestId = createGuest();
guestCreationProgressDialog.dismiss();
if (guestId == UserHandle.USER_NULL) {
Toast.makeText(mContext,
com.android.settingslib.R.string.add_guest_failed,
Toast.LENGTH_SHORT).show();
}
callback.accept(guestId);
mUiExecutor.execute(() -> {
guestCreationProgressDialog.dismiss();
if (guestId == UserHandle.USER_NULL) {
Toast.makeText(mContext,
com.android.settingslib.R.string.add_guest_failed,
Toast.LENGTH_SHORT).show();
}
callback.accept(guestId);
});
});
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.user;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import com.android.internal.util.UserIcons;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.settingslib.utils.ThreadUtils;
import java.util.function.Consumer;
import javax.inject.Inject;
/**
* A class to do the user creation process. It shows a progress dialog, and manages the user
* creation
*/
public class UserCreator {
private final Context mContext;
private final UserManager mUserManager;
@Inject
public UserCreator(Context context, UserManager userManager) {
mContext = context;
mUserManager = userManager;
}
/**
* Shows a progress dialog then starts the user creation process on the main thread.
*
* @param successCallback is called when the user creation is successful.
* @param errorCallback is called when userManager.createUser returns null.
* (Exceptions are not handled by this class)
*/
public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
Runnable errorCallback) {
Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
userCreationProgressDialog.show();
// userManager.createUser will block the thread so post is needed for the dialog to show
ThreadUtils.postOnMainThread(() -> {
UserInfo user =
mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
if (user == null) {
// Couldn't create user for some reason
userCreationProgressDialog.dismiss();
errorCallback.run();
return;
}
Drawable newUserIcon = userIcon;
Resources res = mContext.getResources();
if (newUserIcon == null) {
newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false);
}
mUserManager.setUserIcon(
user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon));
userCreationProgressDialog.dismiss();
successCallback.accept(user);
});
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.user
import android.app.Dialog
import android.content.Context
import android.content.pm.UserInfo
import android.graphics.drawable.Drawable
import android.os.UserManager
import com.android.internal.util.UserIcons
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
/**
* A class to do the user creation process. It shows a progress dialog, and manages the user
* creation
*/
class UserCreator @Inject constructor(
private val context: Context,
private val userManager: UserManager,
@Main private val mainExecutor: Executor,
@Background private val bgExecutor: Executor
) {
/**
* Shows a progress dialog then starts the user creation process on the main thread.
*
* @param successCallback is called when the user creation is successful.
* @param errorCallback is called when userManager.createUser returns null.
* (Exceptions are not handled by this class)
*/
fun createUser(
userName: String?,
userIcon: Drawable?,
successCallback: Consumer<UserInfo?>,
errorCallback: Runnable
) {
val userCreationProgressDialog: Dialog = UserCreatingDialog(context)
userCreationProgressDialog.show()
// userManager.createUser will block the thread so post is needed for the dialog to show
bgExecutor.execute {
val user = userManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0)
mainExecutor.execute main@{
if (user == null) {
// Couldn't create user for some reason
userCreationProgressDialog.dismiss()
errorCallback.run()
return@main
}
bgExecutor.execute {
var newUserIcon = userIcon
val res = context.resources
if (newUserIcon == null) {
newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false)
}
userManager.setUserIcon(
user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon))
}
userCreationProgressDialog.dismiss()
successCallback.accept(user)
}
}
}
}

View File

@@ -77,6 +77,7 @@ import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -269,6 +270,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
bgExecutor.runAllReady()
uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -294,6 +297,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
bgExecutor.runAllReady()
uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(dialogShower).dismiss()
}
@@ -584,4 +589,24 @@ class UserSwitcherControllerTest : SysuiTestCase() {
broadcastReceiverCaptor.value.onReceive(context, intent)
verify(cb).onUserSwitched()
}
@Test
fun onUserItemClicked_guest_runsOnBgThread() {
val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
val guestUserRecord = UserSwitcherController.UserRecord(
null,
picture,
true /* guest */,
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
assertTrue(bgExecutor.numPending() > 0)
verify(userManager, never()).createGuest(context)
bgExecutor.runAllReady()
verify(userManager).createGuest(context)
}
}

View File

@@ -0,0 +1,73 @@
package com.android.systemui.user
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class UserCreatorTest : SysuiTestCase() {
companion object {
const val USER_NAME = "abc"
}
@Mock
private lateinit var userCreator: UserCreator
@Mock
private lateinit var userManager: UserManager
private lateinit var mainExecutor: FakeExecutor
private lateinit var bgExecutor: FakeExecutor
private lateinit var user: UserInfo
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mainExecutor = FakeExecutor(FakeSystemClock())
bgExecutor = FakeExecutor(FakeSystemClock())
userCreator = UserCreator(context, userManager, mainExecutor, bgExecutor)
user = Mockito.mock(UserInfo::class.java)
Mockito.`when`(userManager.createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0))
.thenReturn(user)
}
@Test
fun testCreateUser_threadingOrder() {
val successCallback = Mockito.mock(Consumer::class.java)
val errorCallback = Mockito.mock(Runnable::class.java)
userCreator.createUser(
USER_NAME,
null,
successCallback as Consumer<UserInfo?>,
errorCallback)
verify(userManager, never()).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
bgExecutor.runAllReady()
verify(successCallback, never()).accept(user)
mainExecutor.runAllReady()
verify(userManager, never()).setUserIcon(anyInt(), any(Bitmap::class.java))
bgExecutor.runAllReady()
verify(userManager).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
verify(userManager).setUserIcon(anyInt(), any(Bitmap::class.java))
verify(successCallback).accept(user)
}
}