summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Brad Hinegardner <bhinegardner@google.com> 2023-03-22 18:11:18 -0400
committer Brad Hinegardner <bhinegardner@google.com> 2023-03-28 15:30:02 +0000
commit7f687ebfe412946a10a73db97f8676a42dbaa511 (patch)
tree3e0012b5d29001f77d412ec156d9ebecb0e49f85
parent8df7edf0e3c0eeea75f9f1a1a000991c073e5ef7 (diff)
Change Fullscreen UserSwitcher into a dialog
This is done to retain the last-active-app-resumption when switching users. Also mitigates some issues with the previous activity behavior in "lockTaskMode". Bug: b/270495254 Fixes: b/274122173 Test: UserInteractorTest.kt Test: manual - switch user from chip bar to other non-supervised user Test: manual - switch user to locked-down supervised user. verify that user switcher still appears on locked-down child user and that the user can switch back to the main user. Test: manual - switch user to guest user, and back Test: manual - open fullscreen user switcher, turn off screen. verify that user switcher is dismissed. Test: manual - verify user switcher fullscreen is the expected UI in portrait and landscape. Change-Id: Icc65e464c4f08817fc3668b2e898dadfc7cea4aa
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/docs/user-switching.md6
-rw-r--r--packages/SystemUI/res/values/styles.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt150
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt47
16 files changed, 205 insertions, 302 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index aadc14061a61..5491b4cbe666 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -893,18 +893,6 @@
android:visibleToInstantApps="true">
</activity>
- <activity android:name=".user.UserSwitcherActivity"
- android:label="@string/accessibility_multi_user_switch_switcher"
- android:theme="@style/Theme.UserSwitcherActivity"
- android:excludeFromRecents="true"
- android:showWhenLocked="true"
- android:showForAllUsers="true"
- android:finishOnTaskLaunch="true"
- android:lockTaskMode="always"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
- android:visibleToInstantApps="true">
- </activity>
-
<receiver android:name=".controls.management.ControlsRequestReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
index b9509eb41c3c..01cba426f782 100644
--- a/packages/SystemUI/docs/user-switching.md
+++ b/packages/SystemUI/docs/user-switching.md
@@ -6,7 +6,7 @@ Multiple users and the ability to switch between them is controlled by Settings
### Quick Settings
-In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherActivity][5]).
+In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherFullscreenDialog][5]).
### Bouncer
@@ -29,7 +29,7 @@ All visual implementations should derive their logic and use the adapter specifi
## Visual Components
-### [UserSwitcherActivity][5]
+### [UserSwitcherFullscreenDialog][5]
A fullscreen user switching activity, supporting add guest/user actions if configured.
@@ -41,5 +41,5 @@ Renders user switching as a dialog over the current surface, and supports add gu
[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
-[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
[6]: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1da1a294f9ba..27b15c926bbb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -836,12 +836,10 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
- <style name="Theme.UserSwitcherActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+ <style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
<item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
<item name="android:navigationBarColor">@color/user_switcher_fullscreen_bg</item>
- <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
- <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
<style name="Theme.CreateUser" parent="@android:style/Theme.DeviceDefault.NoActionBar">
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 8387c1dd60a5..b394a079fb00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -89,7 +89,7 @@ interface FooterActionsInteractor {
fun showSettings(expandable: Expandable)
/** Show the user switcher. */
- fun showUserSwitcher(context: Context, expandable: Expandable)
+ fun showUserSwitcher(expandable: Expandable)
}
@SysUISingleton
@@ -177,7 +177,7 @@ constructor(
)
}
- override fun showUserSwitcher(context: Context, expandable: Expandable) {
- userInteractor.showUserSwitcher(context, expandable)
+ override fun showUserSwitcher(expandable: Expandable) {
+ userInteractor.showUserSwitcher(expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index f170ac1d9d4e..3a9098ab49d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -230,7 +230,7 @@ class FooterActionsViewModel(
return
}
- footerActionsInteractor.showUserSwitcher(context, expandable)
+ footerActionsInteractor.showUserSwitcher(expandable)
}
private fun onSettingsButtonClicked(expandable: Expandable) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index f7c8bac1b478..b2bf9727b534 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.user;
-import android.app.Activity;
import android.os.UserHandle;
import com.android.settingslib.users.EditUserInfoController;
@@ -24,11 +23,8 @@ import com.android.systemui.user.data.repository.UserRepositoryModule;
import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
import com.android.systemui.user.ui.dialog.UserDialogModule;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
/**
* Dagger module for User related classes.
@@ -49,12 +45,6 @@ public abstract class UserModule {
return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
}
- /** Provides UserSwitcherActivity */
- @Binds
- @IntoMap
- @ClassKey(UserSwitcherActivity.class)
- public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
-
/**
* Provides the {@link UserHandle} for the user associated with this System UI process.
*
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
deleted file mode 100644
index 52b7fb63c1a2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ /dev/null
@@ -1,57 +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.user
-
-import android.os.Bundle
-import android.view.WindowInsets.Type
-import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import dagger.Lazy
-import javax.inject.Inject
-
-/** Support a fullscreen user switcher */
-open class UserSwitcherActivity
-@Inject
-constructor(
- private val falsingCollector: FalsingCollector,
- private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
-) : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.user_switcher_fullscreen)
- window.decorView.windowInsetsController?.let { controller ->
- controller.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- controller.hide(Type.systemBars())
- }
- val viewModel =
- ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
- UserSwitcherViewBinder.bind(
- view = requireViewById(R.id.user_switcher_root),
- viewModel = viewModel,
- lifecycleOwner = this,
- layoutInflater = layoutInflater,
- falsingCollector = falsingCollector,
- onFinish = this::finish,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
new file mode 100644
index 000000000000..72786efc416d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowInsets
+import android.view.WindowInsetsController
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+
+class UserSwitchFullscreenDialog(
+ context: Context,
+ private val falsingCollector: FalsingCollector,
+ private val userSwitcherViewModel: UserSwitcherViewModel,
+) : SystemUIDialog(context, R.style.Theme_UserSwitcherFullscreenDialog) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+
+ window?.decorView?.windowInsetsController?.let { controller ->
+ controller.systemBarsBehavior =
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ controller.hide(WindowInsets.Type.systemBars())
+ }
+
+ val view =
+ LayoutInflater.from(this.context).inflate(R.layout.user_switcher_fullscreen, null)
+ setContentView(view)
+
+ UserSwitcherViewBinder.bind(
+ view = requireViewById(R.id.user_switcher_root),
+ viewModel = userSwitcherViewModel,
+ layoutInflater = layoutInflater,
+ falsingCollector = falsingCollector,
+ onFinish = this::dismiss,
+ )
+ }
+
+ override fun getWidth(): Int {
+ val displayMetrics = context.resources.displayMetrics.apply {
+ context.display.getRealMetrics(this)
+ }
+ return displayMetrics.widthPixels
+ }
+
+ override fun getHeight() = MATCH_PARENT
+
+} \ No newline at end of file
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 02d447976746..667a3ca8f9d2 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
@@ -48,7 +48,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -502,24 +501,12 @@ constructor(
}
}
- fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ fun showUserSwitcher(expandable: Expandable) {
+ if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+ } else {
showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
- return
}
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
}
private fun showDialog(request: ShowDialogRequestModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 14cc3e783fed..de73cdbb6026 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -50,4 +50,8 @@ sealed class ShowDialogRequestModel(
data class ShowUserSwitcherDialog(
override val expandable: Expandable?,
) : ShowDialogRequestModel()
+
+ data class ShowUserSwitcherFullscreenDialog(
+ override val expandable: Expandable?,
+ ) : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index e13710786fbb..7236e0fd134a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -31,19 +31,18 @@ import android.widget.TextView
import androidx.constraintlayout.helper.widget.Flow as FlowWidget
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.user.UserSwitcherPopupMenu
import com.android.systemui.user.UserSwitcherRootView
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.children
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -56,7 +55,6 @@ object UserSwitcherViewBinder {
fun bind(
view: ViewGroup,
viewModel: UserSwitcherViewModel,
- lifecycleOwner: LifecycleOwner,
layoutInflater: LayoutInflater,
falsingCollector: FalsingCollector,
onFinish: () -> Unit,
@@ -79,88 +77,92 @@ object UserSwitcherViewBinder {
addButton.setOnClickListener { viewModel.onOpenMenuButtonClicked() }
cancelButton.setOnClickListener { viewModel.onCancelButtonClicked() }
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- viewModel.isFinishRequested
- .filter { it }
- .collect {
- onFinish()
- viewModel.onFinished()
- }
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.isFinishRequested
+ .filter { it }
+ .collect {
+ //finish requested, we want to dismiss popupmenu at the same time
+ popupMenu?.dismiss()
+ onFinish()
+ viewModel.onFinished()
+ }
+ }
}
}
- }
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
-
- launch {
- viewModel.isMenuVisible.collect { isVisible ->
- if (isVisible && popupMenu?.isShowing != true) {
- popupMenu?.dismiss()
- // Use post to make sure we show the popup menu *after* the activity is
- // ready to show one to avoid a WindowManager$BadTokenException.
- view.post {
- popupMenu =
- createAndShowPopupMenu(
- context = view.context,
- anchorView = addButton,
- adapter = popupMenuAdapter,
- onDismissed = viewModel::onMenuClosed,
- )
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
+
+ launch {
+ viewModel.isMenuVisible.collect { isVisible ->
+ if (isVisible && popupMenu?.isShowing != true) {
+ popupMenu?.dismiss()
+ // Use post to make sure we show the popup menu *after* the activity is
+ // ready to show one to avoid a WindowManager$BadTokenException.
+ view.post {
+ popupMenu =
+ createAndShowPopupMenu(
+ context = view.context,
+ anchorView = addButton,
+ adapter = popupMenuAdapter,
+ onDismissed = viewModel::onMenuClosed,
+ )
+ }
+ } else if (!isVisible && popupMenu?.isShowing == true) {
+ popupMenu?.dismiss()
+ popupMenu = null
}
- } else if (!isVisible && popupMenu?.isShowing == true) {
- popupMenu?.dismiss()
- popupMenu = null
}
}
- }
- launch {
- viewModel.menu.collect { menuViewModels ->
- popupMenuAdapter.setItems(menuViewModels)
+ launch {
+ viewModel.menu.collect { menuViewModels ->
+ popupMenuAdapter.setItems(menuViewModels)
+ }
}
- }
- launch {
- viewModel.maximumUserColumns.collect { maximumColumns ->
- flowWidget.setMaxElementsWrap(maximumColumns)
+ launch {
+ viewModel.maximumUserColumns.collect { maximumColumns ->
+ flowWidget.setMaxElementsWrap(maximumColumns)
+ }
}
- }
- launch {
- viewModel.users.collect { users ->
- val viewPool =
- gridContainerView.children
- .filter { it.tag == USER_VIEW_TAG }
- .toMutableList()
- viewPool.forEach {
- gridContainerView.removeView(it)
- flowWidget.removeView(it)
- }
- users.forEach { userViewModel ->
- val userView =
- if (viewPool.isNotEmpty()) {
- viewPool.removeAt(0)
- } else {
- val inflatedView =
- layoutInflater.inflate(
- R.layout.user_switcher_fullscreen_item,
- view,
- false,
- )
- inflatedView.tag = USER_VIEW_TAG
- inflatedView
- }
- userView.id = View.generateViewId()
- gridContainerView.addView(userView)
- flowWidget.addView(userView)
- UserViewBinder.bind(
- view = userView,
- viewModel = userViewModel,
- )
+ launch {
+ viewModel.users.collect { users ->
+ val viewPool =
+ gridContainerView.children
+ .filter { it.tag == USER_VIEW_TAG }
+ .toMutableList()
+ viewPool.forEach {
+ gridContainerView.removeView(it)
+ flowWidget.removeView(it)
+ }
+ users.forEach { userViewModel ->
+ val userView =
+ if (viewPool.isNotEmpty()) {
+ viewPool.removeAt(0)
+ } else {
+ val inflatedView =
+ layoutInflater.inflate(
+ R.layout.user_switcher_fullscreen_item,
+ view,
+ false,
+ )
+ inflatedView.tag = USER_VIEW_TAG
+ inflatedView
+ }
+ userView.id = View.generateViewId()
+ gridContainerView.addView(userView)
+ flowWidget.addView(userView)
+ UserViewBinder.bind(
+ view = userView,
+ viewModel = userViewModel,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 79721b370c21..0930cb8a3d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -26,13 +26,16 @@ import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.user.UserSwitchFullscreenDialog
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Provider
@@ -54,6 +57,8 @@ constructor(
private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
private val eventLogger: Lazy<UiEventLogger>,
private val activityStarter: Lazy<ActivityStarter>,
+ private val falsingCollector: Lazy<FalsingCollector>,
+ private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>,
) : CoreStartable {
private var currentDialog: Dialog? = null
@@ -124,6 +129,15 @@ constructor(
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog ->
+ Pair(
+ UserSwitchFullscreenDialog(
+ context = context.get(),
+ falsingCollector = falsingCollector.get(),
+ userSwitcherViewModel = userSwitcherViewModel.get(),
+ ),
+ null, /* dialogCuj */
+ )
}
currentDialog = dialog
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
index 3300e8e5b2a5..78edad7c3af2 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -55,5 +55,5 @@ constructor(
interactor.selectedUser.mapLatest { userModel -> userModel.image }
/** Action to execute on click. Should launch the user switcher */
- val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 37115ad53880..afd72e7ed1be 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -17,12 +17,10 @@
package com.android.systemui.user.ui.viewmodel
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -36,12 +34,13 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
/** Models UI state for the user switcher feature. */
+@SysUISingleton
class UserSwitcherViewModel
-private constructor(
+@Inject
+constructor(
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
- private val powerInteractor: PowerInteractor,
-) : ViewModel() {
+) {
/** On-device users. */
val users: Flow<List<UserViewModel>> =
@@ -112,34 +111,15 @@ private constructor(
}
}
- private fun createFinishRequestedFlow(): Flow<Boolean> {
- var mostRecentSelectedUserId: Int? = null
- var mostRecentIsInteractive: Boolean? = null
-
- return combine(
- // When the user is switched, we should finish.
- userInteractor.selectedUser
- .map { it.id }
- .map {
- val selectedUserChanged =
- mostRecentSelectedUserId != null && mostRecentSelectedUserId != it
- mostRecentSelectedUserId = it
- selectedUserChanged
- },
- // When the screen turns off, we should finish.
- powerInteractor.isInteractive.map {
- val screenTurnedOff = mostRecentIsInteractive == true && !it
- mostRecentIsInteractive = it
- screenTurnedOff
- },
+ private fun createFinishRequestedFlow(): Flow<Boolean> =
+ combine(
// When the cancel button is clicked, we should finish.
hasCancelButtonBeenClicked,
// If an executed action told us to finish, we should finish,
isFinishRequiredDueToExecutedAction,
- ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
- selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
+ ) { cancelButtonClicked, executedActionFinish ->
+ cancelButtonClicked || executedActionFinish
}
- }
private fun toViewModel(
model: UserModel,
@@ -210,22 +190,4 @@ private constructor(
{ userInteractor.selectUser(model.id) }
}
}
-
- class Factory
- @Inject
- constructor(
- private val userInteractor: UserInteractor,
- private val guestUserInteractor: GuestUserInteractor,
- private val powerInteractor: PowerInteractor,
- ) : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST")
- return UserSwitcherViewModel(
- userInteractor = userInteractor,
- guestUserInteractor = guestUserInteractor,
- powerInteractor = powerInteractor,
- )
- as T
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 3ed6cc88826c..e3b8649718c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.user.domain.interactor
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
-import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.Bitmap
@@ -49,7 +48,6 @@ import com.android.systemui.statusbar.CommandQueue
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.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -57,11 +55,9 @@ import com.android.systemui.user.domain.model.ShowDialogRequestModel
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertNotNull
@@ -800,7 +796,7 @@ class UserInteractorTest : SysuiTestCase() {
fun `show user switcher - full screen disabled - shows dialog switcher`() =
testScope.runTest {
val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
+ underTest.showUserSwitcher(expandable)
val dialogRequest = collectLastValue(underTest.dialogShowRequests)
@@ -813,30 +809,22 @@ class UserInteractorTest : SysuiTestCase() {
}
@Test
- fun `show user switcher - full screen enabled - launches activity`() {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
+ fun `show user switcher - full screen enabled - launches full screen dialog`() =
+ testScope.runTest {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(expandable)
+
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
+
+ // Dialog is shown.
+ assertThat(dialogRequest())
+ .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest()).isNull()
+ }
@Test
fun `users - secondary user - managed profile is not included`() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index e08ebf4a9050..5cd2df99325b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,8 +34,6 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerReposito
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.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -88,7 +86,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -116,7 +113,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
}
keyguardRepository = FakeKeyguardRepository()
- powerRepository = FakePowerRepository()
val refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
@@ -145,7 +141,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
set(Flags.FACE_AUTH_REFACTOR, true)
}
underTest =
- UserSwitcherViewModel.Factory(
+ UserSwitcherViewModel(
userInteractor =
UserInteractor(
applicationContext = context,
@@ -173,13 +169,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
),
- powerInteractor =
- PowerInteractor(
- repository = powerRepository,
- ),
guestUserInteractor = guestUserInteractor,
)
- .create(UserSwitcherViewModel::class.java)
}
@Test
@@ -326,46 +317,12 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
}
@Test
- fun `isFinishRequested - finishes when user is switched`() =
- testScope.runTest {
- val userInfos = setUsers(count = 2)
- val isFinishRequested = mutableListOf<Boolean>()
- val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() =
- testScope.runTest {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- val isFinishRequested = mutableListOf<Boolean>()
- val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- powerRepository.setInteractive(false)
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
fun `isFinishRequested - finishes when cancel button is clicked`() =
testScope.runTest {
setUsers(count = 2)
- powerRepository.setInteractive(true)
val isFinishRequested = mutableListOf<Boolean>()
val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
assertThat(isFinishRequested.last()).isFalse()
underTest.onCancelButtonClicked()