summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Laird <evanlaird@google.com> 2022-11-11 15:17:14 -0500
committer Evan Laird <evanlaird@google.com> 2022-11-16 16:06:36 -0500
commit7292f2549bdfe53b0ae3cc3fe6c0b8cfb82b45e8 (patch)
tree53954b7e1409644c964053cf966e23c08ddf5730
parent27ff98f799ecde53015ff8ed0216aef55c6bfac2 (diff)
Replace StatusBarUserSwitcherController with UserInteractor
Originally, the user chip in the status bar was controlled by StatusBarUserSwitcherController, which was backed by UserInfoControllerImpl. This data source would sometimes show incorrect data for the current user name. Since UserRepository/UserInteractor exist now, this CL hooks up the existing StatusBarUserSwitcherContainer view to a new class, StatusBarUserChipViewModel, which is backed by the new repository class. This allows us to fix 2 bugs at once: 1. The chip now only shows when there are > 1 users AND the config is true 2. Hooking the chip up to the same data source that populates the user switcher screen means that the names will always be consistent Also removed the now-unused StatusBarUserSwitcherController, and its associated feature controller and tracker classes. Test: atest StatusBarUserChipViewModelTest Test: atest UserInteractorRefactoredTest Test: atest KeyguardStatusBarViewControllerTest Test: atest PhoneStatusBarViewControllerTest Bug: 254246505 Bug: 254680435 Change-Id: I3f5e212fed485b5697dedbe0510ab6c34d3b2ade
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt306
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt2
18 files changed, 487 insertions, 479 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index 8fc86004c400..a7d4455b43c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -21,10 +21,7 @@ import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -50,10 +47,4 @@ public abstract class KeyguardStatusBarViewModule {
static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
return view.findViewById(R.id.user_switcher_container);
}
-
- /** */
- @Binds
- @KeyguardStatusBarViewScope
- abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 7a49a495155b..3c989a4ed302 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -48,6 +48,9 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -70,7 +73,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
- private ViewGroup mUserSwitcherContainer;
+ private StatusBarUserSwitcherContainer mUserSwitcherContainer;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mKeyguardUserAvatarEnabled;
@@ -121,8 +124,12 @@ public class KeyguardStatusBarView extends RelativeLayout {
loadDimens();
}
- public ViewGroup getUserSwitcherContainer() {
- return mUserSwitcherContainer;
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController}
+ * @param viewModel view model for the status bar user chip
+ */
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 14cebf4b9f4b..d4dc1dc197a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,13 +59,11 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
@@ -110,9 +108,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
private final UserManager mUserManager;
- private final StatusBarUserSwitcherFeatureController mFeatureController;
- private final StatusBarUserSwitcherController mUserSwitcherController;
- private final StatusBarUserInfoTracker mStatusBarUserInfoTracker;
+ private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
@@ -276,9 +272,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
SysuiStatusBarStateController statusBarStateController,
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
UserManager userManager,
- StatusBarUserSwitcherFeatureController featureController,
- StatusBarUserSwitcherController userSwitcherController,
- StatusBarUserInfoTracker statusBarUserInfoTracker,
+ StatusBarUserChipViewModel userChipViewModel,
SecureSettings secureSettings,
CommandQueue commandQueue,
@Main Executor mainExecutor,
@@ -301,9 +295,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
mUserManager = userManager;
- mFeatureController = featureController;
- mUserSwitcherController = userSwitcherController;
- mStatusBarUserInfoTracker = statusBarUserInfoTracker;
+ mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
@@ -328,8 +320,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
R.dimen.header_notifications_collide_distance);
mView.setKeyguardUserAvatarEnabled(
- !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
- mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
+ !mStatusBarUserChipViewModel.getChipEnabled());
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
mDisableStateTracker = new DisableStateTracker(
@@ -344,11 +335,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
super.onInit();
mCarrierTextController.init();
mBatteryMeterViewController.init();
- mUserSwitcherController.init();
}
@Override
protected void onViewAttached() {
+ mView.init(mStatusBarUserChipViewModel);
mConfigurationController.addCallback(mConfigurationListener);
mAnimationScheduler.addCallback(mAnimationCallback);
mUserInfoController.addCallback(mOnUserInfoChangedListener);
@@ -394,9 +385,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
/** Sets whether user switcher is enabled. */
public void setKeyguardUserSwitcherEnabled(boolean enabled) {
mView.setKeyguardUserSwitcherEnabled(enabled);
- // We don't have a listener for when the user switcher setting changes, so this is
- // where we re-check the state
- mStatusBarUserInfoTracker.checkEnabled();
}
/** Sets whether this controller should listen to battery updates. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7aeb08dd5ddb..28bc64de2cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,6 +38,9 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -73,6 +76,11 @@ public class PhoneStatusBarView extends FrameLayout {
mTouchEventHandler = handler;
}
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
+ StatusBarUserChipViewBinder.bind(container, viewModel);
+ }
+
@Override
public void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f9c4c8f3b4fe..a6c2b2c2771c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,11 +23,11 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
@@ -40,7 +40,7 @@ class PhoneStatusBarViewController private constructor(
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
@@ -91,10 +91,10 @@ class PhoneStatusBarViewController private constructor(
init {
mView.setTouchEventHandler(touchEventHandler)
+ mView.init(userChipViewModel)
}
override fun onInit() {
- userSwitcherController.init()
}
fun setImportantForAccessibility(mode: Int) {
@@ -156,9 +156,9 @@ class PhoneStatusBarViewController private constructor(
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
) {
fun create(
view: PhoneStatusBarView,
@@ -168,7 +168,7 @@ class PhoneStatusBarViewController private constructor(
view,
progressProvider.getOrNull(),
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
touchEventHandler,
configurationController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 41f1f9589ce4..efec27099dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -29,8 +29,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -39,7 +37,6 @@ import java.util.Set;
import javax.inject.Named;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
@@ -126,12 +123,6 @@ public interface StatusBarFragmentModule {
}
/** */
- @Binds
- @StatusBarFragmentScope
- StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
-
- /** */
@Provides
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
deleted file mode 100644
index f6b8cb0782cd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.phone.userswitcher
-
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Since every user switcher chip will user the exact same information and logic on whether or not
- * to show, and what data to show, it makes sense to create a single tracker here
- */
-@SysUISingleton
-class StatusBarUserInfoTracker @Inject constructor(
- private val userInfoController: UserInfoController,
- private val userManager: UserManager,
- private val dumpManager: DumpManager,
- @Main private val mainExecutor: Executor,
- @Background private val backgroundExecutor: Executor
-) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
- var currentUserName: String? = null
- private set
- var currentUserAvatar: Drawable? = null
- private set
- var userSwitcherEnabled = false
- private set
- private var listening = false
-
- private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>()
-
- private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ ->
- currentUserAvatar = picture
- currentUserName = name
- notifyListenersUserInfoChanged()
- }
-
- init {
- dumpManager.registerDumpable(TAG, this)
- }
-
- override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
- if (listeners.isEmpty()) {
- startListening()
- }
-
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) {
- listeners.remove(listener)
-
- if (listeners.isEmpty()) {
- stopListening()
- }
- }
-
- private fun notifyListenersUserInfoChanged() {
- listeners.forEach {
- it.onCurrentUserChipInfoUpdated()
- }
- }
-
- private fun notifyListenersSettingChanged() {
- listeners.forEach {
- it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled)
- }
- }
-
- private fun startListening() {
- listening = true
- userInfoController.addCallback(userInfoChangedListener)
- }
-
- private fun stopListening() {
- listening = false
- userInfoController.removeCallback(userInfoChangedListener)
- }
-
- /**
- * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
- * changed
- */
- fun checkEnabled() {
- backgroundExecutor.execute {
- // Check on a background thread to avoid main thread Binder calls
- val wasEnabled = userSwitcherEnabled
- userSwitcherEnabled = userManager.isUserSwitcherEnabled
-
- if (wasEnabled != userSwitcherEnabled) {
- mainExecutor.execute {
- notifyListenersSettingChanged()
- }
- }
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(" userSwitcherEnabled=$userSwitcherEnabled")
- pw.println(" listening=$listening")
- }
-}
-
-interface CurrentUserChipInfoUpdatedListener {
- fun onCurrentUserChipInfoUpdated()
- fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
-}
-
-private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
deleted file mode 100644
index e498ae451400..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.view.View
-import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.UserSwitcherActivity
-import com.android.systemui.util.ViewController
-
-import javax.inject.Inject
-
-/**
- * ViewController for [StatusBarUserSwitcherContainer]
- */
-class StatusBarUserSwitcherControllerImpl @Inject constructor(
- view: StatusBarUserSwitcherContainer,
- private val tracker: StatusBarUserInfoTracker,
- private val featureController: StatusBarUserSwitcherFeatureController,
- private val userSwitcherDialogController: UserSwitchDialogController,
- private val featureFlags: FeatureFlags,
- private val activityStarter: ActivityStarter,
- private val falsingManager: FalsingManager
-) : ViewController<StatusBarUserSwitcherContainer>(view),
- StatusBarUserSwitcherController {
- private val listener = object : CurrentUserChipInfoUpdatedListener {
- override fun onCurrentUserChipInfoUpdated() {
- updateChip()
- }
-
- override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener {
- override fun onUserSwitcherPreferenceChange(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- public override fun onViewAttached() {
- tracker.addCallback(listener)
- featureController.addCallback(featureFlagListener)
- mView.setOnClickListener { view: View ->
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return@setOnClickListener
- }
-
- if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- val intent = Intent(context, UserSwitcherActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-
- activityStarter.startActivity(intent, true /* dismissShade */,
- null /* ActivityLaunchAnimator.Controller */,
- true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
- } else {
- userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
- }
- }
-
- updateEnabled()
- }
-
- override fun onViewDetached() {
- tracker.removeCallback(listener)
- featureController.removeCallback(featureFlagListener)
- mView.setOnClickListener(null)
- }
-
- private fun updateChip() {
- mView.text.text = tracker.currentUserName
- mView.avatar.setImageDrawable(tracker.currentUserAvatar)
- }
-
- private fun updateEnabled() {
- if (featureController.isStatusBarUserSwitcherFeatureEnabled() &&
- tracker.userSwitcherEnabled) {
- mView.visibility = View.VISIBLE
- updateChip()
- } else {
- mView.visibility = View.GONE
- }
- }
-}
-
-interface StatusBarUserSwitcherController {
- fun init()
-}
-
-private const val TAG = "SbUserSwitcherController"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
deleted file mode 100644
index 7bae9ff72760..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.phone.userswitcher
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.policy.CallbackController
-
-import javax.inject.Inject
-
-@SysUISingleton
-class StatusBarUserSwitcherFeatureController @Inject constructor(
- private val flags: FeatureFlags
-) : CallbackController<OnUserSwitcherPreferenceChangeListener> {
- private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>()
-
- init {
- flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) {
- it.requestNoRestart()
- notifyListeners()
- }
- }
-
- fun isStatusBarUserSwitcherFeatureEnabled(): Boolean {
- return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- }
-
- override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- listeners.remove(listener)
- }
-
- private fun notifyListeners() {
- val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- listeners.forEach {
- it.onUserSwitcherPreferenceChange(enabled)
- }
- }
-}
-
-interface OnUserSwitcherPreferenceChangeListener {
- fun onUserSwitcherPreferenceChange(enabled: Boolean)
-}
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 ed53de7dbee7..4c9b8e4639ca 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
@@ -23,6 +23,7 @@ import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -79,6 +80,9 @@ interface UserRepository {
/** Whether we've scheduled the creation of a guest user. */
val isGuestUserCreationScheduled: AtomicBoolean
+ /** Whether to enable the status bar user chip (which launches the user switcher) */
+ val isStatusBarUserChipEnabled: Boolean
+
/** The user of the secondary service. */
var secondaryUserId: Int
@@ -127,6 +131,9 @@ constructor(
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override val isStatusBarUserChipEnabled: Boolean =
+ appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index bce91cb2d9ae..83f0711caa38 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -261,6 +261,9 @@ constructor(
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+ /** Whether to enable the user chip in the status bar */
+ val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
new file mode 100644
index 000000000000..8e40f68e27e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.user.ui.binder
+
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@OptIn(InternalCoroutinesApi::class)
+object StatusBarUserChipViewBinder {
+ /** Binds the status bar user chip view model to the given view */
+ @JvmStatic
+ fun bind(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible }
+ }
+
+ launch {
+ viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) }
+ }
+
+ launch {
+ viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) }
+ }
+
+ bindButton(view, viewModel)
+ }
+ }
+ }
+
+ private fun bindButton(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
new file mode 100644
index 000000000000..3300e8e5b2a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarUserChipViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: UserInteractor,
+) {
+ /** Whether the status bar chip ui should be available */
+ val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+
+ /** Whether or not the chip should be showing, based on the number of users */
+ val isChipVisible: Flow<Boolean> =
+ if (!chipEnabled) {
+ flowOf(false)
+ } else {
+ interactor.users.mapLatest { users -> users.size > 1 }
+ }
+
+ /** The display name of the current user */
+ val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name }
+
+ /** Avatar for the current user */
+ val userAvatar: Flow<Drawable> =
+ interactor.selectedUser.mapLatest { userModel -> userModel.image }
+
+ /** Action to execute on click. Should launch the user switcher */
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 6ec5cf82a81e..eb0b9b3a3fb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -56,14 +56,12 @@ import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -112,16 +110,12 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
@Mock
private UserManager mUserManager;
+ @Mock
+ private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
@Captor
private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock
- private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController;
- @Mock
- private StatusBarUserSwitcherController mStatusBarUserSwitcherController;
- @Mock
- private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
@Mock private KeyguardLogger mLogger;
@@ -169,9 +163,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
mStatusBarStateController,
mStatusBarContentInsetsProvider,
mUserManager,
- mStatusBarUserSwitcherFeatureController,
- mStatusBarUserSwitcherController,
- mStatusBarUserInfoTracker,
+ mStatusBarUserChipViewModel,
mSecureSettings,
mCommandQueue,
mFakeExecutor,
@@ -479,8 +471,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOn() {
// GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(true);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
// WHEN the controller is created
mController = createController();
@@ -492,8 +483,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOff() {
// GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(false);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
// WHEN the controller is created
mController = createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index a61fba5c4000..320a08315843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,11 +27,11 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -64,7 +64,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
- private lateinit var userSwitcherController: StatusBarUserSwitcherController
+ private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
private lateinit var viewUtil: ViewUtil
@@ -79,14 +79,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
`when`(notificationPanelViewController.view).thenReturn(panelView)
`when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
.thenReturn(moveFromCenterAnimation)
- // create the view on main thread as it requires main looper
+ // create the view and controller on main thread as it requires main looper
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ controller = createAndInitController(view)
}
-
- controller = createAndInitController(view)
}
@Test
@@ -106,7 +105,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true
- controller = createAndInitController(view)
+ // create the controller on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
argumentCaptor.value.onPreDraw()
@@ -126,7 +128,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
configurationController
).create(view, touchEventHandler).also {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index eba3b04f3472..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-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.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-@SmallTest
-class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock
- private lateinit var tracker: StatusBarUserInfoTracker
-
- @Mock
- private lateinit var featureController: StatusBarUserSwitcherFeatureController
-
- @Mock
- private lateinit var userSwitcherDialogController: UserSwitchDialogController
-
- @Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
- private lateinit var activityStarter: ActivityStarter
-
- @Mock
- private lateinit var falsingManager: FalsingManager
-
- private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer
- private lateinit var controller: StatusBarUserSwitcherControllerImpl
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null)
- statusBarUserSwitcherContainer
- controller = StatusBarUserSwitcherControllerImpl(
- statusBarUserSwitcherContainer,
- tracker,
- featureController,
- userSwitcherDialogController,
- featureFlags,
- activityStarter,
- falsingManager
- )
- controller.init()
- controller.onViewAttached()
- }
-
- @Test
- fun testFalsingManager() {
- statusBarUserSwitcherContainer.callOnClick()
- verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY)
- }
-
- @Test
- fun testStartActivity() {
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
- statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(), any())
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
- statusBarUserSwitcherContainer.callOnClick()
- verify(activityStarter).startActivity(any(Intent::class.java),
- eq(true) /* dismissShade */,
- eq(null) /* animationController */,
- eq(true) /* showOverLockscreenWhenLocked */,
- eq(UserHandle.SYSTEM))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
new file mode 100644
index 000000000000..db348b8029a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StatusBarUserChipViewModelTest : SysuiTestCase() {
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+
+ private lateinit var underTest: StatusBarUserChipViewModel
+
+ private val userRepository = FakeUserRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private lateinit var guestUserInteractor: GuestUserInteractor
+ private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ doAnswer { invocation ->
+ val userId = invocation.arguments[0] as Int
+ when (userId) {
+ USER_ID_0 -> return@doAnswer USER_IMAGE_0
+ USER_ID_1 -> return@doAnswer USER_IMAGE_1
+ USER_ID_2 -> return@doAnswer USER_IMAGE_2
+ else -> return@doAnswer mock<Bitmap>()
+ }
+ }
+ .`when`(manager)
+ .getUserIcon(anyInt())
+
+ userRepository.isStatusBarUserChipEnabled = true
+
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ repository = userRepository,
+ )
+ guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+ )
+
+ underTest = viewModel()
+ }
+
+ @Test
+ fun `config is false - chip is disabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = false
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isFalse()
+ }
+
+ @Test
+ fun `config is true - chip is enabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = true
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isTrue()
+ }
+
+ @Test
+ fun `should show chip criteria - single user`() =
+ testScope.runTest {
+ userRepository.setUserInfos(listOf(USER_0))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ val values = mutableListOf<Boolean>()
+
+ val job = launch { underTest.isChipVisible.toList(values) }
+ advanceUntilIdle()
+
+ assertThat(values).containsExactly(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `should show chip criteria - multiple users`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Boolean? = null
+ val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip name - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Text? = null
+ val job = underTest.userName.onEach { latest = it }.launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_NAME_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_NAME_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_NAME_2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip avatar - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ // A little hacky. System server passes us bitmaps and we wrap them in the interactor.
+ // Unwrap them to make sure we're always tracking the current user's bitmap
+ var latest: Bitmap? = null
+ val job =
+ underTest.userAvatar
+ .onEach {
+ if (it !is BitmapDrawable) {
+ latest = null
+ }
+
+ latest = (it as BitmapDrawable).bitmap
+ }
+ .launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_IMAGE_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_IMAGE_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_IMAGE_2)
+
+ job.cancel()
+ }
+
+ private fun viewModel(): StatusBarUserChipViewModel {
+ return StatusBarUserChipViewModel(
+ context = context,
+ interactor =
+ UserInteractor(
+ applicationContext = context,
+ repository = userRepository,
+ activityStarter = activityStarter,
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ ),
+ featureFlags = featureFlags,
+ manager = manager,
+ applicationScope = testScope.backgroundScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = testDispatcher,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor = guestUserInteractor,
+ )
+ )
+ }
+
+ private suspend fun setMultipleUsers() {
+ userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ }
+
+ companion object {
+ private const val USER_ID_0 = 0
+ private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_0 = Text.Loaded("zero")
+
+ private const val USER_ID_1 = 1
+ private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_1 = Text.Loaded("one")
+
+ private const val USER_ID_2 = 2
+ private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_2 = Text.Loaded("two")
+
+ private val USER_0 =
+ UserInfo(
+ USER_ID_0,
+ USER_NAME_0.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_1 =
+ UserInfo(
+ USER_ID_1,
+ USER_NAME_1.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_2 =
+ UserInfo(
+ USER_ID_2,
+ USER_NAME_2.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index b7c8cbf40bea..ea5a302ce05a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -49,6 +49,8 @@ class FakeUserRepository : UserRepository {
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override var isStatusBarUserChipEnabled: Boolean = false
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false