From 0582bb68ae9184bd14023ee411d71d88bec127fe Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Wed, 14 Sep 2022 13:11:41 -0700 Subject: [PATCH 1/4] Cleans up UserSwitcherController API. * Removes unused method and classes * Makes externally-unused methods non-public Test: Built and ran System UI, manually interacted with user switcher UI through the lock-screen and full-screen experiences Bug: 246631653 Change-Id: I0d6a8e4ffc899f358533486d8c4b0d38de6e5519 --- .../android/systemui/statusbar/UserUtil.java | 62 ------------------- .../policy/UserSwitcherController.java | 52 +++++----------- .../KeyguardSecurityContainerTest.java | 4 +- .../KeyguardQsUserSwitchControllerTest.kt | 5 +- 4 files changed, 20 insertions(+), 103 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java deleted file mode 100644 index 4551807499abe..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2016 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.statusbar; - -import android.content.Context; -import android.content.DialogInterface; - -import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.policy.UserSwitcherController; - -public class UserUtil { - public static void deleteUserWithPrompt(Context context, int userId, - UserSwitcherController userSwitcherController) { - new RemoveUserDialog(context, userId, userSwitcherController).show(); - } - - private final static class RemoveUserDialog extends SystemUIDialog implements - DialogInterface.OnClickListener { - - private final int mUserId; - private final UserSwitcherController mUserSwitcherController; - - public RemoveUserDialog(Context context, int userId, - UserSwitcherController userSwitcherController) { - super(context); - setTitle(R.string.user_remove_user_title); - setMessage(context.getString(R.string.user_remove_user_message)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString(R.string.user_remove_user_remove), this); - setCanceledOnTouchOutside(false); - mUserId = userId; - mUserSwitcherController = userSwitcherController; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == BUTTON_NEUTRAL) { - cancel(); - } else { - dismiss(); - mUserSwitcherController.removeUserId(mUserId); - } - } - } -} 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 1d5b88e7dee01..c36abbd4916fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -158,7 +158,6 @@ public class UserSwitcherController implements Dumpable { @VisibleForTesting Dialog mAddUserDialog; private int mLastNonGuestUser = UserHandle.USER_SYSTEM; - private boolean mResumeUserOnGuestLogout = true; private boolean mSimpleUserSwitcher; // When false, there won't be any visual affordance to add a new user from the keyguard even if // the user is unlocked @@ -431,38 +430,41 @@ public class UserSwitcherController implements Dumpable { }); } - boolean systemCanCreateUsers() { + private boolean systemCanCreateUsers() { return !mUserManager.hasBaseUserRestriction( UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); } - boolean currentUserCanCreateUsers() { + private boolean currentUserCanCreateUsers() { UserInfo currentUser = mUserTracker.getUserInfo(); return currentUser != null && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM) && systemCanCreateUsers(); } - boolean anyoneCanCreateUsers() { + private boolean anyoneCanCreateUsers() { return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue(); } + @VisibleForTesting boolean canCreateGuest(boolean hasExistingGuest) { return mUserSwitcherEnabled && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) && !hasExistingGuest; } + @VisibleForTesting boolean canCreateUser() { return mUserSwitcherEnabled && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); } - boolean createIsRestricted() { + private boolean createIsRestricted() { return !mAddUsersFromLockScreen.getValue(); } + @VisibleForTesting boolean canCreateSupervisedUser() { return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser(); } @@ -489,30 +491,14 @@ public class UserSwitcherController implements Dumpable { return mSimpleUserSwitcher; } - public void setResumeUserOnGuestLogout(boolean resume) { - mResumeUserOnGuestLogout = resume; - } - /** * Returns whether the current user is a system user. */ - public boolean isSystemUser() { + @VisibleForTesting + boolean isSystemUser() { return mUserTracker.getUserId() == UserHandle.USER_SYSTEM; } - public void removeUserId(int userId) { - if (userId == UserHandle.USER_SYSTEM) { - Log.w(TAG, "User " + userId + " could not removed."); - return; - } - if (mUserTracker.getUserId() == userId) { - switchToUserId(UserHandle.USER_SYSTEM); - } - if (mUserManager.removeUser(userId)) { - refreshUsers(UserHandle.USER_NULL); - } - } - /** * @return UserRecord for the current user */ @@ -604,7 +590,7 @@ public class UserSwitcherController implements Dumpable { switchToUserId(id); } - protected void switchToUserId(int id) { + private void switchToUserId(int id) { try { if (mView != null) { mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder @@ -621,7 +607,7 @@ public class UserSwitcherController implements Dumpable { private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) { int newId = UserHandle.USER_SYSTEM; - if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { + if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { newId = info.id; @@ -818,12 +804,10 @@ public class UserSwitcherController implements Dumpable { refreshUsers(UserHandle.USER_ALL); } - @VisibleForTesting public void addAdapter(WeakReference adapter) { mAdapters.add(adapter); } - @VisibleForTesting public ArrayList getUsers() { return mUsers; } @@ -921,7 +905,7 @@ public class UserSwitcherController implements Dumpable { int newUserId = UserHandle.USER_SYSTEM; if (targetUserId == UserHandle.USER_NULL) { // when target user is not specified switch to last non guest user - if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { + if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { newUserId = info.id; @@ -1014,7 +998,7 @@ public class UserSwitcherController implements Dumpable { * @return The multi-user user ID of the newly created guest user, or * {@link UserHandle#USER_NULL} if the guest couldn't be created. */ - public @UserIdInt int createGuest() { + private @UserIdInt int createGuest() { UserInfo guest; try { guest = mUserManager.createGuest(mContext); @@ -1036,9 +1020,9 @@ public class UserSwitcherController implements Dumpable { mView = view; } - @VisibleForTesting - public KeyguardStateController getKeyguardStateController() { - return mKeyguardStateController; + /** Returns {@code true} if the keyguard is showing; {@code false} otherwise */ + public boolean isKeyguardShowing() { + return mKeyguardStateController.isShowing(); } /** @@ -1059,11 +1043,9 @@ public class UserSwitcherController implements Dumpable { public static abstract class BaseUserAdapter extends BaseAdapter { final UserSwitcherController mController; - private final KeyguardStateController mKeyguardStateController; protected BaseUserAdapter(UserSwitcherController controller) { mController = controller; - mKeyguardStateController = controller.getKeyguardStateController(); controller.addAdapter(new WeakReference<>(this)); } @@ -1081,7 +1063,7 @@ public class UserSwitcherController implements Dumpable { } private int countUsers(boolean includeGuest) { - boolean keyguardShowing = mKeyguardStateController.isShowing(); + boolean keyguardShowing = mController.isKeyguardShowing(); final int userSize = getUsers().size(); int count = 0; for (int i = 0; i < userSize; i++) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 28e99da494966..43f6f1aac0974 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -116,9 +116,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User"); - when(mUserSwitcherController.getKeyguardStateController()) - .thenReturn(mKeyguardStateController); - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true); mScreenWidth = getUiDevice().getDisplayWidth(); mFakeMeasureSpec = View diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index b4f3987b2f957..b86ca6fc53757 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -38,9 +38,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -102,8 +102,7 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { ViewUtils.attachView(view) testableLooper.processAllMessages() - `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController) - `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true) + `when`(userSwitcherController.isKeyguardShowing).thenReturn(true) `when`(keyguardStateController.isShowing).thenReturn(true) `when`(keyguardStateController.isKeyguardGoingAway).thenReturn(false) keyguardQsUserSwitchController.init() From e112db8ac61fca2a5a1d1c73b56b2223d93f816a Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Thu, 15 Sep 2022 08:07:14 -0700 Subject: [PATCH 2/4] Extracts interface from UserSwitcherController. Our goal with this refactor is to provide a differnt implementation of UserSwitcherController that uses modern architecture inside itself. To achieve that, we need to be able to provide a new implementation alongside the existing implementation and switch between them based on our feature flags. This CL extracts an interface our of the UserSwitcherController class, also named UserSwitcherController (to minimize changes to downstream customers of this class) and moves the current implementation to UserSwitcherControllerOldImpl. As a side-effect, we also had to move the old UserSwitcherController.BaseUserAdapter out into its own class, BaseUserSwitcherAdapter. This CL was done almost entirely automatically using tools provided by the Android Studio IDE. There are no logical changes in this CL at all. Bug: 246631653 Test: Manually verified that user switcher works properly in full-screen, from quick settings, and from the bouncer. Change-Id: I383f1bb9147c7afc3047bcf2ad7422b7c6894821 --- packages/SystemUI/ktfmt_includes.txt | 4 +- .../keyguard/KeyguardSecurityContainer.java | 9 +- .../systemui/qs/tiles/UserDetailView.java | 9 +- .../phone/MultiUserSwitchController.java | 5 +- .../policy/BaseUserSwitcherAdapter.kt | 119 ++++++++ .../KeyguardQsUserSwitchController.java | 4 +- .../KeyguardUserSwitcherController.java | 17 +- .../policy/UserSwitcherController.kt | 182 ++++++++++++ ...ava => UserSwitcherControllerOldImpl.java} | 275 ++++-------------- .../policy/dagger/StatusBarPolicyModule.java | 6 + .../systemui/user/UserSwitcherActivity.kt | 197 ++++++------- .../user/data/repository/UserRepository.kt | 2 +- ...usBarUserSwitcherControllerOldImplTest.kt} | 4 +- .../policy/BaseUserSwitcherAdapterTest.kt | 264 +++++++++++++++++ ...t => UserSwitcherControllerOldImplTest.kt} | 10 +- .../data/repository/UserRepositoryImplTest.kt | 2 +- 16 files changed, 737 insertions(+), 372 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt rename packages/SystemUI/src/com/android/systemui/statusbar/policy/{UserSwitcherController.java => UserSwitcherControllerOldImpl.java} (84%) rename packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/{StatusBarUserSwitcherControllerTest.kt => StatusBarUserSwitcherControllerOldImplTest.kt} (98%) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt rename packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/{UserSwitcherControllerTest.kt => UserSwitcherControllerOldImplTest.kt} (99%) diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 818248439d0ab..9f275afa37656 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -813,7 +813,7 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -828,7 +828,7 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index f73c98e4971b2..a0cfd41b999fd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -22,8 +22,6 @@ import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; import static java.lang.Integer.max; @@ -87,8 +85,8 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter; import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; @@ -1137,7 +1135,7 @@ public class KeyguardSecurityContainer extends FrameLayout { KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor); - BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) { + BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { UserRecord item = getItem(position); @@ -1172,8 +1170,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } textView.setSelected(item == currentUser); view.setEnabled(item.isSwitchToEnabled); - view.setAlpha(view.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : - USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(view); return view; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 0ec4eef1e5516..97476b2d1cdee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -16,9 +16,6 @@ package com.android.systemui.qs.tiles; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -42,6 +39,7 @@ import com.android.systemui.qs.PseudoGridView; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.data.source.UserRecord; @@ -73,7 +71,8 @@ public class UserDetailView extends PseudoGridView { mAdapter.refresh(); } - public static class Adapter extends UserSwitcherController.BaseUserAdapter + /** Provides views for user detail items. */ + public static class Adapter extends BaseUserSwitcherAdapter implements OnClickListener { private final Context mContext; @@ -137,7 +136,7 @@ public class UserDetailView extends PseudoGridView { v.setActivated(item.isCurrent); v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); - v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(v); if (item.isCurrent) { mCurrentUserView = v; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java index 4d6168989691a..00c3e8fac0b4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.FooterActionsView; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.UserSwitcherActivity; import com.android.systemui.util.ViewController; @@ -49,7 +50,7 @@ public class MultiUserSwitchController extends ViewController { private final ActivityStarter mActivityStarter; private final FeatureFlags mFeatureFlags; - private UserSwitcherController.BaseUserAdapter mUserListener; + private BaseUserSwitcherAdapter mUserListener; private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override @@ -135,7 +136,7 @@ public class MultiUserSwitchController extends ViewController { final UserSwitcherController controller = mUserSwitcherController; if (controller != null) { - mUserListener = new UserSwitcherController.BaseUserAdapter(controller) { + mUserListener = new BaseUserSwitcherAdapter(controller) { @Override public void notifyDataSetChanged() { mView.refreshContentDescription(getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt new file mode 100644 index 0000000000000..5b2d69564585b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 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.statusbar.policy + +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.widget.BaseAdapter +import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserRecordName +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserSwitcherActionIconResourceId +import java.lang.ref.WeakReference + +/** Provides views for user switcher experiences. */ +abstract class BaseUserSwitcherAdapter +protected constructor( + protected val controller: UserSwitcherController, +) : BaseAdapter() { + + protected open val users: ArrayList + get() = controller.users + + init { + controller.addAdapter(WeakReference(this)) + } + + override fun getCount(): Int { + return if (controller.isKeyguardShowing) { + users.count { !it.isRestricted } + } else { + users.size + } + } + + override fun getItem(position: Int): UserRecord { + return users[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + /** + * Notifies that a user item in the UI has been clicked. + * + * If the user switcher is hosted in a dialog, passing a non-null [dialogShower] will allow + * animation to and from the parent dialog. + */ + @JvmOverloads + fun onUserListItemClicked( + record: UserRecord, + dialogShower: DialogShower? = null, + ) { + controller.onUserListItemClicked(record, dialogShower) + } + + open fun getName(context: Context, item: UserRecord): String { + return getName(context, item, false) + } + + /** Returns the name for the given {@link UserRecord}. */ + open fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { + return getUserRecordName( + context = context, + record = item, + isGuestUserAutoCreated = controller.isGuestUserAutoCreated, + isGuestUserResetting = controller.isGuestUserResetting, + isTablet = isTablet, + ) + } + + fun refresh() { + controller.refreshUsers(UserHandle.USER_NULL) + } + + companion object { + @JvmStatic + protected val disabledUserAvatarColorFilter: ColorFilter by lazy { + val matrix = ColorMatrix() + matrix.setSaturation(0f) // 0 - grayscale + ColorMatrixColorFilter(matrix) + } + + @JvmStatic + @JvmOverloads + protected fun getIconDrawable( + context: Context, + item: UserRecord, + isTablet: Boolean = false, + ): Drawable { + val iconRes = + getUserSwitcherActionIconResourceId( + item.isAddUser, + item.isGuest, + item.isAddSupervisedUser, + isTablet, + ) + return checkNotNull(context.getDrawable(iconRes)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 163060814545e..dc73d1f007c6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -69,7 +69,7 @@ public class KeyguardQsUserSwitchController extends ViewController private final Context mContext; private Resources mResources; private final UserSwitcherController mUserSwitcherController; - private UserSwitcherController.BaseUserAdapter mAdapter; + private BaseUserSwitcherAdapter mAdapter; private final KeyguardStateController mKeyguardStateController; private final FalsingManager mFalsingManager; protected final SysuiStatusBarStateController mStatusBarStateController; @@ -171,7 +171,7 @@ public class KeyguardQsUserSwitchController extends ViewController mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); mUserAvatarViewWithBackground = mView.findViewById( R.id.kg_multi_user_avatar_with_background); - mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { + mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { return null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e2f5734cb4f91..0995a00533a8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.policy; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -232,14 +229,8 @@ public class KeyguardUserSwitcherController extends ViewController - *
  • {@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}
  • - *
  • {@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}
  • - * - * - * @return true if the user switcher should be open by default on the lock screen. * @see android.os.UserManager#isUserSwitcherEnabled() */ public boolean isSimpleUserSwitcher() { @@ -436,7 +427,7 @@ public class KeyguardUserSwitcherController extends ViewController + + /** Whether the user switcher experience should use the simple experience. */ + val isSimpleUserSwitcher: Boolean + + /** Require a view for jank detection */ + fun init(view: View) + + /** The [UserRecord] of the current user or `null` when none. */ + val currentUserRecord: UserRecord? + + /** The name of the current user of the device or `null`, when none is selected. */ + val currentUserName: String? + + /** + * Notifies that a user has been selected. + * + * This will trigger the right user journeys to create a guest user, switch users, and/or + * navigate to the correct destination. + * + * If a user with the given ID is not found, this method is a no-op. + * + * @param userId The ID of the user to switch to. + * @param dialogShower An optional [DialogShower] in case we need to show dialogs. + */ + fun onUserSelected(userId: Int, dialogShower: DialogShower?) + + /** Whether it is allowed to add users while the device is locked. */ + val isAddUsersFromLockScreenEnabled: Flow + + /** Whether the guest user is configured to always be present on the device. */ + val isGuestUserAutoCreated: Boolean + + /** Whether the guest user is currently being reset. */ + val isGuestUserResetting: Boolean + + /** Creates and switches to the guest user. */ + fun createAndSwitchToGuestUser(dialogShower: DialogShower?) + + /** Shows the add user dialog. */ + fun showAddUserDialog(dialogShower: DialogShower?) + + /** Starts an activity to add a supervised user to the device. */ + fun startSupervisedUserActivity() + + /** Notifies when the display density or font scale has changed. */ + fun onDensityOrFontScaleChanged() + + /** Registers an adapter to notify when the users change. */ + fun addAdapter(adapter: WeakReference) + + /** Notifies the item for a user has been clicked. */ + fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?) + + /** + * Removes guest user and switches to target user. The guest must be the current user and its id + * must be `guestUserId`. + * + * If `targetUserId` is `UserHandle.USER_NULL`, then create a new guest user in the foreground, + * and immediately switch to it. This is used for wiping the current guest and replacing it with + * a new one. + * + * If `targetUserId` is specified, then remove the guest in the background while switching to + * `targetUserId`. + * + * If device is configured with `config_guestUserAutoCreated`, then after guest user is removed, + * a new one is created in the background. This has no effect if `targetUserId` is + * `UserHandle.USER_NULL`. + * + * @param guestUserId id of the guest user to remove + * @param targetUserId id of the user to switch to after guest is removed. If + * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user. + */ + fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) + + /** + * Exits guest user and switches to previous non-guest user. The guest must be the current user. + * + * @param guestUserId user id of the guest user to exit + * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when + * target user id is not known + * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest + * only if its ephemeral, else keep guest + */ + fun exitGuestUser( + @UserIdInt guestUserId: Int, + @UserIdInt targetUserId: Int, + forceRemoveGuestOnExit: Boolean + ) + + /** + * Guarantee guest is present only if the device is provisioned. Otherwise, create a content + * observer to wait until the device is provisioned, then schedule the guest creation. + */ + fun schedulePostBootGuestCreation() + + /** Whether keyguard is showing. */ + val isKeyguardShowing: Boolean + + /** Returns the [EnforcedAdmin] for the given record, or `null` if there isn't one. */ + fun getEnforcedAdmin(record: UserRecord): EnforcedAdmin? + + /** Returns `true` if the given record is disabled by the admin; `false` otherwise. */ + fun isDisabledByAdmin(record: UserRecord): Boolean + + /** Starts an activity with the given [Intent]. */ + fun startActivity(intent: Intent) + + /** + * Refreshes users from UserManager. + * + * The pictures are only loaded if they have not been loaded yet. + * + * @param forcePictureLoadForId forces the picture of the given user to be reloaded. + */ + fun refreshUsers(forcePictureLoadForId: Int) + + /** Adds a subscriber to when user switches. */ + fun addUserSwitchCallback(callback: UserSwitchCallback) + + /** Removes a previously-added subscriber. */ + fun removeUserSwitchCallback(callback: UserSwitchCallback) + + /** Defines interface for classes that can be called back when the user is switched. */ + fun interface UserSwitchCallback { + /** Notifies that the user has switched. */ + fun onUserSwitched() + } + + companion object { + /** Alpha value to apply to a user view in the user switcher when it's selectable. */ + private const val ENABLED_ALPHA = + LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA + + /** Alpha value to apply to a user view in the user switcher when it's not selectable. */ + private const val DISABLED_ALPHA = + LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA + + @JvmStatic + fun setSelectableAlpha(view: View) { + view.alpha = + if (view.isEnabled) { + ENABLED_ALPHA + } else { + DISABLED_ALPHA + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java similarity index 84% rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java rename to packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java index c36abbd4916fd..d365aa6f952d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java @@ -11,9 +11,8 @@ * 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 + * limitations under the License. */ - package com.android.systemui.statusbar.policy; import static android.os.UserManager.SWITCHABILITY_STATUS_OK; @@ -34,10 +33,6 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -51,7 +46,6 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.View; import android.view.WindowManagerGlobal; -import android.widget.BaseAdapter; import android.widget.Toast; import androidx.annotation.Nullable; @@ -63,7 +57,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.systemui.Dumpable; import com.android.systemui.GuestResetOrExitSessionReceiver; import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.R; @@ -86,7 +79,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; import com.android.systemui.user.data.source.UserRecord; -import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -106,15 +98,14 @@ import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlowKt; /** - * Keeps a list of all users on the device for user switching. + * Old implementation. Keeps a list of all users on the device for user switching. + * + * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController} + * instead. */ +@Deprecated @SysUISingleton -public class UserSwitcherController implements Dumpable { - - public static final float USER_SWITCH_ENABLED_ALPHA = - LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA; - public static final float USER_SWITCH_DISABLED_ALPHA = - LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA; +public class UserSwitcherControllerOldImpl implements UserSwitcherController { private static final String TAG = "UserSwitcherController"; private static final boolean DEBUG = false; @@ -123,7 +114,7 @@ public class UserSwitcherController implements Dumpable { private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000; private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; - private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l; + private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L; private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"; private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"; @@ -132,7 +123,7 @@ public class UserSwitcherController implements Dumpable { protected final UserTracker mUserTracker; protected final UserManager mUserManager; private final ContentObserver mSettingsObserver; - private final ArrayList> mAdapters = new ArrayList<>(); + private final ArrayList> mAdapters = new ArrayList<>(); @VisibleForTesting final GuestResumeSessionReceiver mGuestResumeSessionReceiver; @VisibleForTesting @@ -186,7 +177,8 @@ public class UserSwitcherController implements Dumpable { Collections.synchronizedList(new ArrayList<>()); @Inject - public UserSwitcherController(Context context, + public UserSwitcherControllerOldImpl( + Context context, IActivityManager activityManager, UserManager userManager, UserTracker userTracker, @@ -302,16 +294,10 @@ public class UserSwitcherController implements Dumpable { refreshUsers(UserHandle.USER_NULL); } - /** - * Refreshes users from UserManager. - * - * The pictures are only loaded if they have not been loaded yet. - * - * @param forcePictureLoadForId forces the picture of the given user to be reloaded. - */ + @Override @SuppressWarnings("unchecked") - private void refreshUsers(int forcePictureLoadForId) { - if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")"); + public void refreshUsers(int forcePictureLoadForId) { + if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")"); if (forcePictureLoadForId != UserHandle.USER_NULL) { mForcePictureLoadForUserId.put(forcePictureLoadForId, true); } @@ -322,8 +308,8 @@ public class UserSwitcherController implements Dumpable { boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL); SparseArray bitmaps = new SparseArray<>(mUsers.size()); - final int N = mUsers.size(); - for (int i = 0; i < N; i++) { + final int userCount = mUsers.size(); + for (int i = 0; i < userCount; i++) { UserRecord r = mUsers.get(i); if (r == null || r.picture == null || r.info == null || forceAllUsers || mForcePictureLoadForUserId.get(r.info.id)) { @@ -478,7 +464,7 @@ public class UserSwitcherController implements Dumpable { private void notifyAdapters() { for (int i = mAdapters.size() - 1; i >= 0; i--) { - BaseUserAdapter adapter = mAdapters.get(i).get(); + BaseUserSwitcherAdapter adapter = mAdapters.get(i).get(); if (adapter != null) { adapter.notifyDataSetChanged(); } else { @@ -487,6 +473,7 @@ public class UserSwitcherController implements Dumpable { } } + @Override public boolean isSimpleUserSwitcher() { return mSimpleUserSwitcher; } @@ -499,9 +486,7 @@ public class UserSwitcherController implements Dumpable { return mUserTracker.getUserId() == UserHandle.USER_SYSTEM; } - /** - * @return UserRecord for the current user - */ + @Override public @Nullable UserRecord getCurrentUserRecord() { for (int i = 0; i < mUsers.size(); ++i) { UserRecord userRecord = mUsers.get(i); @@ -512,17 +497,7 @@ public class UserSwitcherController implements Dumpable { return null; } - /** - * Notifies that a user has been selected. - * - *

    This will trigger the right user journeys to create a guest user, switch users, and/or - * navigate to the correct destination. - * - *

    If a user with the given ID is not found, this method is a no-op. - * - * @param userId The ID of the user to switch to. - * @param dialogShower An optional {@link DialogShower} in case we need to show dialogs. - */ + @Override public void onUserSelected(int userId, @Nullable DialogShower dialogShower) { UserRecord userRecord = mUsers.stream() .filter(x -> x.resolveId() == userId) @@ -535,23 +510,23 @@ public class UserSwitcherController implements Dumpable { onUserListItemClicked(userRecord, dialogShower); } - /** Whether it is allowed to add users while the device is locked. */ - public Flow getAddUsersFromLockScreen() { + @Override + public Flow isAddUsersFromLockScreenEnabled() { return mAddUsersFromLockScreen; } - /** Returns {@code true} if the guest user is configured to always be present on the device. */ + @Override public boolean isGuestUserAutoCreated() { return mGuestUserAutoCreated; } - /** Returns {@code true} if the guest user is currently being reset. */ + @Override public boolean isGuestUserResetting() { return mGuestIsResetting.get(); } - @VisibleForTesting - void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { + @Override + public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { if (record.isGuest && record.info == null) { createAndSwitchToGuestUser(dialogShower); } else if (record.isAddUser) { @@ -631,9 +606,7 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Creates and switches to the guest user. - */ + @Override public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) { createGuestAsync(guestId -> { // guestId may be USER_NULL if we haven't reloaded the user list yet. @@ -644,9 +617,7 @@ public class UserSwitcherController implements Dumpable { }); } - /** - * Shows the add user dialog. - */ + @Override public void showAddUserDialog(@Nullable DialogShower dialogShower) { if (mAddUserDialog != null && mAddUserDialog.isShowing()) { mAddUserDialog.cancel(); @@ -663,9 +634,7 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Starts an activity to add a supervised user to the device. - */ + @Override public void startSupervisedUserActivity() { final Intent intent = new Intent() .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) @@ -697,7 +666,7 @@ public class UserSwitcherController implements Dumpable { public void onReceive(Context context, Intent intent) { if (DEBUG) { Log.v(TAG, "Broadcast: a=" + intent.getAction() - + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); + + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); } boolean unpauseRefreshUsers = false; @@ -711,8 +680,8 @@ public class UserSwitcherController implements Dumpable { final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); final UserInfo userInfo = mUserManager.getUserInfo(currentId); - final int N = mUsers.size(); - for (int i = 0; i < N; i++) { + final int userCount = mUsers.size(); + for (int i = 0; i < userCount; i++) { UserRecord record = mUsers.get(i); if (record.info == null) continue; boolean shouldBeCurrent = record.info.id == currentId; @@ -791,7 +760,7 @@ public class UserSwitcherController implements Dumpable { pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated); } - /** Returns the name of the current user of the phone. */ + @Override public String getCurrentUserName() { if (mUsers.isEmpty()) return null; UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null); @@ -800,38 +769,22 @@ public class UserSwitcherController implements Dumpable { return item.info.name; } + @Override public void onDensityOrFontScaleChanged() { refreshUsers(UserHandle.USER_ALL); } - public void addAdapter(WeakReference adapter) { + @Override + public void addAdapter(WeakReference adapter) { mAdapters.add(adapter); } + @Override public ArrayList getUsers() { return mUsers; } - /** - * Removes guest user and switches to target user. The guest must be the current user and its id - * must be {@code guestUserId}. - * - *

    If {@code targetUserId} is {@link UserHandle#USER_NULL}, then create a new guest user in - * the foreground, and immediately switch to it. This is used for wiping the current guest and - * replacing it with a new one. - * - *

    If {@code targetUserId} is specified, then remove the guest in the background while - * switching to {@code targetUserId}. - * - *

    If device is configured with {@link - * com.android.internal.R.bool.config_guestUserAutoCreated}, then after guest user is removed, a - * new one is created in the background. This has no effect if {@code targetUserId} is {@link - * UserHandle#USER_NULL}. - * - * @param guestUserId id of the guest user to remove - * @param targetUserId id of the user to switch to after guest is removed. If {@link - * UserHandle#USER_NULL}, then switch immediately to the newly created guest user. - */ + @Override public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) { UserInfo currentUser = mUserTracker.getUserInfo(); if (currentUser.id != guestUserId) { @@ -878,18 +831,9 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Exits guest user and switches to previous non-guest user. The guest must be the current - * user. - * - * @param guestUserId user id of the guest user to exit - * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when - * target user id is not known - * @param forceRemoveGuestOnExit true: remove guest before switching user, - * false: remove guest only if its ephemeral, else keep guest - */ + @Override public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId, - boolean forceRemoveGuestOnExit) { + boolean forceRemoveGuestOnExit) { UserInfo currentUser = mUserTracker.getUserInfo(); if (currentUser.id != guestUserId) { Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" @@ -943,10 +887,7 @@ public class UserSwitcherController implements Dumpable { } - /** - * Guarantee guest is present only if the device is provisioned. Otherwise, create a content - * observer to wait until the device is provisioned, then schedule the guest creation. - */ + @Override public void schedulePostBootGuestCreation() { if (isDeviceAllowedToAddGuest()) { guaranteeGuestPresent(); @@ -1013,133 +954,27 @@ public class UserSwitcherController implements Dumpable { return guest.id; } - /** - * Require a view for jank detection - */ + @Override public void init(View view) { mView = view; } - /** Returns {@code true} if the keyguard is showing; {@code false} otherwise */ + @Override public boolean isKeyguardShowing() { return mKeyguardStateController.isShowing(); } - /** - * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one. - */ + @Override @Nullable public EnforcedAdmin getEnforcedAdmin(UserRecord record) { return mEnforcedAdminByUserRecord.get(record); } - /** - * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise. - */ + @Override public boolean isDisabledByAdmin(UserRecord record) { return mDisabledByAdmin.contains(record); } - public static abstract class BaseUserAdapter extends BaseAdapter { - - final UserSwitcherController mController; - - protected BaseUserAdapter(UserSwitcherController controller) { - mController = controller; - controller.addAdapter(new WeakReference<>(this)); - } - - protected ArrayList getUsers() { - return mController.getUsers(); - } - - public int getUserCount() { - return countUsers(false); - } - - @Override - public int getCount() { - return countUsers(true); - } - - private int countUsers(boolean includeGuest) { - boolean keyguardShowing = mController.isKeyguardShowing(); - final int userSize = getUsers().size(); - int count = 0; - for (int i = 0; i < userSize; i++) { - if (getUsers().get(i).isGuest && !includeGuest) { - continue; - } - if (getUsers().get(i).isRestricted && keyguardShowing) { - break; - } - count++; - } - return count; - } - - @Override - public UserRecord getItem(int position) { - return getUsers().get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - /** - * It handles click events on user list items. - * - * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower} - * will allow animation to and from the parent dialog. - * - */ - public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) { - mController.onUserListItemClicked(record, dialogShower); - } - - public void onUserListItemClicked(UserRecord record) { - onUserListItemClicked(record, null); - } - - public String getName(Context context, UserRecord item) { - return getName(context, item, false); - } - - /** - * Returns the name for the given {@link UserRecord}. - */ - public String getName(Context context, UserRecord item, boolean isTablet) { - return LegacyUserUiHelper.getUserRecordName( - context, - item, - mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting(), - isTablet); - } - - protected static ColorFilter getDisabledUserAvatarColorFilter() { - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0f); // 0 - grayscale - return new ColorMatrixColorFilter(matrix); - } - - protected static Drawable getIconDrawable(Context context, UserRecord item) { - return getIconDrawable(context, item, false); - } - protected static Drawable getIconDrawable(Context context, UserRecord item, - boolean isTablet) { - int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); - return context.getDrawable(iconRes); - } - - public void refresh() { - mController.refreshUsers(UserHandle.USER_NULL); - } - } - private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) { EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId()); @@ -1160,20 +995,17 @@ public class UserSwitcherController implements Dumpable { defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0; } + @Override public void startActivity(Intent intent) { mActivityStarter.startActivity(intent, true); } - /** - * Add a subscriber to when user switches. - */ + @Override public void addUserSwitchCallback(UserSwitchCallback callback) { mUserSwitchCallbacks.add(callback); } - /** - * Remove a subscriber to when user switches. - */ + @Override public void removeUserSwitchCallback(UserSwitchCallback callback) { mUserSwitchCallbacks.remove(callback); } @@ -1200,7 +1032,7 @@ public class UserSwitcherController implements Dumpable { // which // helps making the transition faster. if (!mKeyguardStateController.isShowing()) { - mHandler.post(UserSwitcherController.this::notifyAdapters); + mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters); } else { notifyAdapters(); } @@ -1349,13 +1181,4 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Callback to for when this controller receives the intent to switch users. - */ - public interface UserSwitchCallback { - /** - * Called when user has switched. - */ - void onUserSwitched(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 1b7353923adaf..5e86b1ff880ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -58,6 +58,8 @@ import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.UserSwitcherControllerOldImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; @@ -196,4 +198,8 @@ public interface StatusBarPolicyModule { static DataSaverController provideDataSaverController(NetworkController networkController) { return networkController.getDataSaverController(); } + + /** Binds {@link UserSwitcherController} to its implementation. */ + @Binds + UserSwitcherController bindUserSwitcherController(UserSwitcherControllerOldImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 5e2dde6be0462..108ab43977e9c 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -53,10 +53,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter -import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA -import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.ui.binder.UserSwitcherViewBinder import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel @@ -66,10 +64,10 @@ import kotlin.math.ceil private const val USER_VIEW = "user_view" -/** - * Support a fullscreen user switcher - */ -open class UserSwitcherActivity @Inject constructor( +/** Support a fullscreen user switcher */ +open class UserSwitcherActivity +@Inject +constructor( private val userSwitcherController: UserSwitcherController, private val broadcastDispatcher: BroadcastDispatcher, private val falsingCollector: FalsingCollector, @@ -86,11 +84,12 @@ open class UserSwitcherActivity @Inject constructor( private lateinit var addButton: View private var addUserRecords = mutableListOf() private val onBackCallback = OnBackInvokedCallback { finish() } - private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - finish() + private val userSwitchedCallback: UserTracker.Callback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + finish() + } } - } // When the add users options become available, insert another option to manage users private val manageUserRecord = UserRecord( @@ -114,13 +113,14 @@ open class UserSwitcherActivity @Inject constructor( @VisibleForTesting fun createActivity() { setContentView(R.layout.user_switcher_fullscreen) - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) + window.decorView.systemUiVisibility = + (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) if (isUsingModernArchitecture()) { Log.d(TAG, "Using modern architecture.") - val viewModel = ViewModelProvider( - this, viewModelFactory.get())[UserSwitcherViewModel::class.java] + val viewModel = + ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] UserSwitcherViewBinder.bind( view = requireViewById(R.id.user_switcher_root), viewModel = viewModel, @@ -136,27 +136,23 @@ open class UserSwitcherActivity @Inject constructor( parent = requireViewById(R.id.user_switcher_root) - parent.touchHandler = object : Gefingerpoken { - override fun onTouchEvent(ev: MotionEvent?): Boolean { - falsingCollector.onTouchEvent(ev) - return false + parent.touchHandler = + object : Gefingerpoken { + override fun onTouchEvent(ev: MotionEvent?): Boolean { + falsingCollector.onTouchEvent(ev) + return false + } } - } - requireViewById(R.id.cancel).apply { - setOnClickListener { - _ -> finish() - } - } + requireViewById(R.id.cancel).apply { setOnClickListener { _ -> finish() } } - addButton = requireViewById(R.id.add).apply { - setOnClickListener { - _ -> showPopupMenu() - } - } + addButton = + requireViewById(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } } onBackInvokedDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback) + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + onBackCallback + ) userSwitcherController.init(parent) initBroadcastReceiver() @@ -169,25 +165,30 @@ open class UserSwitcherActivity @Inject constructor( val items = mutableListOf() addUserRecords.forEach { items.add(it) } - var popupMenuAdapter = ItemAdapter( - this, - R.layout.user_switcher_fullscreen_popup_item, - layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, - { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply { - setTint(resources.getColor( - R.color.user_switcher_fullscreen_popup_item_tint, - getTheme() - )) - } } - ) + var popupMenuAdapter = + ItemAdapter( + this, + R.layout.user_switcher_fullscreen_popup_item, + layoutInflater, + { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, + { item: UserRecord -> + adapter.findUserIcon(item, true).mutate().apply { + setTint( + resources.getColor( + R.color.user_switcher_fullscreen_popup_item_tint, + getTheme() + ) + ) + } + } + ) popupMenuAdapter.addAll(items) - popupMenu = UserSwitcherPopupMenu(this).apply { - setAnchorView(addButton) - setAdapter(popupMenuAdapter) - setOnItemClickListener { - parent: AdapterView<*>, view: View, pos: Int, id: Long -> + popupMenu = + UserSwitcherPopupMenu(this).apply { + setAnchorView(addButton) + setAdapter(popupMenuAdapter) + setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long -> if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) { return@setOnItemClickListener } @@ -206,10 +207,10 @@ open class UserSwitcherActivity @Inject constructor( if (!item.isAddUser) { this@UserSwitcherActivity.finish() } - } + } - show() - } + show() + } } private fun buildUserViews() { @@ -227,8 +228,8 @@ open class UserSwitcherActivity @Inject constructor( val totalWidth = parent.width val userViewCount = adapter.getTotalUserViews() val maxColumns = getMaxColumns(userViewCount) - val horizontalGap = resources - .getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) + val horizontalGap = + resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns @@ -299,14 +300,15 @@ open class UserSwitcherActivity @Inject constructor( } private fun initBroadcastReceiver() { - broadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.getAction() - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - finish() + broadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.getAction() + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + finish() + } } } - } val filter = IntentFilter() filter.addAction(Intent.ACTION_SCREEN_OFF) @@ -322,9 +324,7 @@ open class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } - /** - * Provides views to populate the option menu. - */ + /** Provides views to populate the option menu. */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -337,43 +337,27 @@ open class UserSwitcherActivity @Inject constructor( val item = getItem(position) val view = convertView ?: layoutInflater.inflate(resource, parent, false) - view.requireViewById(R.id.icon).apply { - setImageDrawable(iconGetter(item)) - } - view.requireViewById(R.id.text).apply { - setText(textGetter(item)) - } + view.requireViewById(R.id.icon).apply { setImageDrawable(iconGetter(item)) } + view.requireViewById(R.id.text).apply { setText(textGetter(item)) } return view } } - private inner class UserAdapter : BaseUserAdapter(userSwitcherController) { + private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val item = getItem(position) var view = convertView as ViewGroup? if (view == null) { - view = layoutInflater.inflate( - R.layout.user_switcher_fullscreen_item, - parent, - false - ) as ViewGroup - } - (view.getChildAt(0) as ImageView).apply { - setImageDrawable(getDrawable(item)) - } - (view.getChildAt(1) as TextView).apply { - setText(getName(getContext(), item)) + view = + layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false) + as ViewGroup } + (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) } + (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) } view.setEnabled(item.isSwitchToEnabled) - view.setAlpha( - if (view.isEnabled()) { - USER_SWITCH_ENABLED_ALPHA - } else { - USER_SWITCH_DISABLED_ALPHA - } - ) + UserSwitcherController.setSelectableAlpha(view) view.setTag(USER_VIEW) return view } @@ -401,23 +385,20 @@ open class UserSwitcherActivity @Inject constructor( } fun getTotalUserViews(): Int { - return users.count { item -> - !doNotRenderUserView(item) - } + return users.count { item -> !doNotRenderUserView(item) } } fun doNotRenderUserView(item: UserRecord): Boolean { - return item.isAddUser || - item.isAddSupervisedUser || - item.isGuest && item.info == null + return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null } private fun getDrawable(item: UserRecord): Drawable { - var drawable = if (item.isGuest) { - getDrawable(R.drawable.ic_account_circle) - } else { - findUserIcon(item) - } + var drawable = + if (item.isGuest) { + getDrawable(R.drawable.ic_account_circle) + } else { + findUserIcon(item) + } drawable.mutate() if (!item.isCurrent && !item.isSwitchToEnabled) { @@ -429,16 +410,16 @@ open class UserSwitcherActivity @Inject constructor( ) } - val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() - as LayerDrawable - if (item == userSwitcherController.getCurrentUserRecord()) { + val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable + if (item == userSwitcherController.currentUserRecord) { (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply { - val stroke = resources - .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) - val color = Utils.getColorAttrDefaultColor( - this@UserSwitcherActivity, - com.android.internal.R.attr.colorAccentPrimary - ) + val stroke = + resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) + val color = + Utils.getColorAttrDefaultColor( + this@UserSwitcherActivity, + com.android.internal.R.attr.colorAccentPrimary + ) setStroke(stroke, color) } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 305b5ee920a16..035638800f9cf 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -99,7 +99,7 @@ constructor( override val actions: Flow> = userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } } - override val isActionableWhenLocked: Flow = controller.addUsersFromLockScreen + override val isActionableWhenLocked: Flow = controller.isAddUsersFromLockScreenEnabled override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt index 37c0f3621b6f1..bf432388ad283 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt @@ -34,14 +34,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @SmallTest -class StatusBarUserSwitcherControllerTest : SysuiTestCase() { +class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() { @Mock private lateinit var tracker: StatusBarUserInfoTracker diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt new file mode 100644 index 0000000000000..f3046477f4d16 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022 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.statusbar.policy + +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.os.UserHandle +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.util.mockito.kotlinArgumentCaptor +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.lang.ref.WeakReference +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class BaseUserSwitcherAdapterTest : SysuiTestCase() { + + @Mock private lateinit var controller: UserSwitcherController + + private lateinit var underTest: BaseUserSwitcherAdapter + + private lateinit var users: ArrayList + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + + whenever(controller.users).thenAnswer { users } + + underTest = + object : BaseUserSwitcherAdapter(controller) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + return mock() + } + } + } + + @Test + fun `Adds self to controller in constructor`() { + val captor = kotlinArgumentCaptor>() + verify(controller).addAdapter(captor.capture()) + + assertThat(captor.value.get()).isEqualTo(underTest) + } + + @Test + fun count() { + assertThat(underTest.count).isEqualTo(users.size) + } + + @Test + fun `count - ignores restricted users when device is locked`() { + whenever(controller.isKeyguardShowing).thenReturn(true) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + isRestricted = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + isRestricted = true, // this one will be ignored. + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + assertThat(underTest.count).isEqualTo(users.size - 1) + } + + @Test + fun `count - does not ignore restricted users when device is not locked`() { + whenever(controller.isKeyguardShowing).thenReturn(false) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + isRestricted = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + isRestricted = true, + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + assertThat(underTest.count).isEqualTo(users.size) + } + + @Test + fun getItem() { + assertThat((0 until underTest.count).map { position -> underTest.getItem(position) }) + .isEqualTo(users) + } + + @Test + fun getItemId() { + (0 until underTest.count).map { position -> + assertThat(underTest.getItemId(position)).isEqualTo(position) + } + } + + @Test + fun onUserListItemClicked() { + val userRecord = users[users.size / 2] + val dialogShower: UserSwitchDialogController.DialogShower = mock() + + underTest.onUserListItemClicked(userRecord, dialogShower) + + verify(controller).onUserListItemClicked(userRecord, dialogShower) + } + + @Test + fun `getName - non guest - returns real name`() { + val userRecord = + createUserRecord( + id = 1, + picture = mock(), + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo(userRecord.info?.name) + } + + @Test + fun `getName - guest and selected - returns exit guest action name`() { + val expected = "Exit guest" + context.orCreateTestableResources.addOverride( + com.android.settingslib.R.string.guest_exit_quick_settings_button, + expected, + ) + + val userRecord = + createUserRecord( + id = 2, + picture = null, + isGuest = true, + isSelected = true, + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo(expected) + } + + @Test + fun `getName - guest and not selected - returns enter guest action name`() { + val expected = "Guest" + context.orCreateTestableResources.addOverride( + com.android.internal.R.string.guest_name, + expected, + ) + + val userRecord = + createUserRecord( + id = 2, + picture = null, + isGuest = true, + isSelected = false, + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo("Guest") + } + + @Test + fun refresh() { + underTest.refresh() + + verify(controller).refreshUsers(UserHandle.USER_NULL) + } + + private fun createUserRecord( + id: Int, + picture: Bitmap? = null, + isSelected: Boolean = false, + isGuest: Boolean = false, + isAction: Boolean = false, + isRestricted: Boolean = false, + ): UserRecord { + return UserRecord( + info = + if (isAction) { + null + } else { + UserInfo(id, "name$id", 0) + }, + picture = picture, + isCurrent = isSelected, + isGuest = isGuest, + isRestricted = isRestricted, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt similarity index 99% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt index 8dcd4bb3b7387..76ecc1c7f36da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt @@ -86,7 +86,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class UserSwitcherControllerTest : SysuiTestCase() { +class UserSwitcherControllerOldImplTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityManager: IActivityManager @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @@ -118,7 +118,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { private lateinit var longRunningExecutor: FakeExecutor private lateinit var uiExecutor: FakeExecutor private lateinit var uiEventLogger: UiEventLoggerFake - private lateinit var userSwitcherController: UserSwitcherController + private lateinit var userSwitcherController: UserSwitcherControllerOldImpl private lateinit var picture: Bitmap private val ownerId = UserHandle.USER_SYSTEM private val ownerInfo = UserInfo(ownerId, "Owner", null, @@ -205,7 +205,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { } private fun setupController() { - userSwitcherController = UserSwitcherController( + userSwitcherController = + UserSwitcherControllerOldImpl( mContext, activityManager, userManager, @@ -230,7 +231,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { dumpManager, dialogLaunchAnimator, guestResumeSessionReceiver, - guestResetOrExitSessionReceiver) + guestResetOrExitSessionReceiver + ) userSwitcherController.init(notificationShadeWindowView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 6b466e1ac2d87..6fec343d036cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -60,7 +60,7 @@ class UserRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(controller.addUsersFromLockScreen).thenReturn(MutableStateFlow(false)) + whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false)) whenever(controller.isGuestUserAutoCreated).thenReturn(false) whenever(controller.isGuestUserResetting).thenReturn(false) From 967590d1778d04b70c799851aa96d350838f1120 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Thu, 15 Sep 2022 10:18:51 -0700 Subject: [PATCH 3/4] Feature flag for UserSwitcherController refactor. We need a flag for the code to decide whether to keep using the old implementation or switch to the new one. Bug: 246631653 Test: N/A Change-Id: Idf15ff5deb6fed47bbc88651ddfc7a04282a2990 --- packages/SystemUI/src/com/android/systemui/flags/Flags.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index cd6c57aea76b7..25d5370e2bc1d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -104,6 +104,10 @@ public class Flags { public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY = new UnreleasedFlag(209, true); + /** Whether the new implementation of UserSwitcherController should be used. */ + public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER = + new UnreleasedFlag(210, false); + /***************************************/ // 300 - power menu public static final ReleasedFlag POWER_MENU_LITE = From df49520f05713aa26b480bc45d495fbd78226faa Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Thu, 15 Sep 2022 10:37:51 -0700 Subject: [PATCH 4/4] Switching UserSwitcherController implementation. Introduces a class that implements UserSwitcherController by checking the feature flags and delegating to the old implementation if the flag is not set. Right now, the class will throw an exception if the flag is set to on because all of its methods are unimplemented. In the next CL(s), we will be implementing all of these. Bug: 246631653 Test: Ran system UI, did some user switching, verified no crashes. Change-Id: Ia5df853c5297ba6a086db867ad39111ff6758b6d --- .../policy/UserSwitcherControllerImpl.kt | 269 ++++++++++++++++++ .../policy/dagger/StatusBarPolicyModule.java | 4 +- 2 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt new file mode 100644 index 0000000000000..12834f68c3b70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2022 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.statusbar.policy + +import android.content.Intent +import android.view.View +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.data.source.UserRecord +import dagger.Lazy +import java.io.PrintWriter +import java.lang.ref.WeakReference +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Implementation of [UserSwitcherController]. */ +class UserSwitcherControllerImpl +@Inject +constructor( + private val flags: FeatureFlags, + @Suppress("DEPRECATION") private val oldImpl: Lazy, +) : UserSwitcherController { + + private val isNewImpl: Boolean + get() = flags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER) + private val _oldImpl: UserSwitcherControllerOldImpl + get() = oldImpl.get() + + private fun notYetImplemented(): Nothing { + error("Not yet implemented!") + } + + override val users: ArrayList + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.users + } + + override val isSimpleUserSwitcher: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isSimpleUserSwitcher + } + + override fun init(view: View) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.init(view) + } + } + + override val currentUserRecord: UserRecord? + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.currentUserRecord + } + + override val currentUserName: String? + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.currentUserName + } + + override fun onUserSelected( + userId: Int, + dialogShower: UserSwitchDialogController.DialogShower? + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onUserSelected(userId, dialogShower) + } + } + + override val isAddUsersFromLockScreenEnabled: Flow + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isAddUsersFromLockScreenEnabled + } + + override val isGuestUserAutoCreated: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isGuestUserAutoCreated + } + + override val isGuestUserResetting: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isGuestUserResetting + } + + override fun createAndSwitchToGuestUser( + dialogShower: UserSwitchDialogController.DialogShower?, + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.createAndSwitchToGuestUser(dialogShower) + } + } + + override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.showAddUserDialog(dialogShower) + } + } + + override fun startSupervisedUserActivity() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.startSupervisedUserActivity() + } + } + + override fun onDensityOrFontScaleChanged() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onDensityOrFontScaleChanged() + } + } + + override fun addAdapter(adapter: WeakReference) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.addAdapter(adapter) + } + } + + override fun onUserListItemClicked( + record: UserRecord, + dialogShower: UserSwitchDialogController.DialogShower?, + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onUserListItemClicked(record, dialogShower) + } + } + + override fun removeGuestUser(guestUserId: Int, targetUserId: Int) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.removeGuestUser(guestUserId, targetUserId) + } + } + + override fun exitGuestUser( + guestUserId: Int, + targetUserId: Int, + forceRemoveGuestOnExit: Boolean + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) + } + } + + override fun schedulePostBootGuestCreation() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.schedulePostBootGuestCreation() + } + } + + override val isKeyguardShowing: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isKeyguardShowing + } + + override fun getEnforcedAdmin(record: UserRecord): RestrictedLockUtils.EnforcedAdmin? { + return if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.getEnforcedAdmin(record) + } + } + + override fun isDisabledByAdmin(record: UserRecord): Boolean { + return if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isDisabledByAdmin(record) + } + } + + override fun startActivity(intent: Intent) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.startActivity(intent) + } + } + + override fun refreshUsers(forcePictureLoadForId: Int) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.refreshUsers(forcePictureLoadForId) + } + } + + override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.addUserSwitchCallback(callback) + } + } + + override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.removeUserSwitchCallback(callback) + } + } + + override fun dump(pw: PrintWriter, args: Array) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.dump(pw, args) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 5e86b1ff880ab..b1b45b51d8e4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -59,7 +59,7 @@ import com.android.systemui.statusbar.policy.SecurityControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.UserSwitcherControllerOldImpl; +import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; @@ -201,5 +201,5 @@ public interface StatusBarPolicyModule { /** Binds {@link UserSwitcherController} to its implementation. */ @Binds - UserSwitcherController bindUserSwitcherController(UserSwitcherControllerOldImpl impl); + UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl); }