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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -125,12 +122,6 @@ public interface StatusBarFragmentModule {
|
||||
return view.findViewById(R.id.user_switcher_container);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
@StatusBarFragmentScope
|
||||
StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
|
||||
StatusBarUserSwitcherControllerImpl controller);
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@StatusBarFragmentScope
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)) }
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user