diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 40281a194b126..c8fddb000ee46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -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); + }); }); } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java deleted file mode 100644 index 0686071c17180..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java +++ /dev/null @@ -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 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); - }); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt new file mode 100644 index 0000000000000..dcbbe74419223 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt @@ -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, + 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) + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index 09d7c03c20919..359a780d2a94e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -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) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt new file mode 100644 index 0000000000000..a85ae7df546b9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt @@ -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, + 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) + } +}