Merge "[flexiglass] Password bouncer isn't side-by-side on unfolded." into main
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index defaa20..4400786 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -142,7 +142,11 @@
     modifier: Modifier = Modifier,
 ) {
     val backgroundColor = MaterialTheme.colorScheme.surface
-    val layout = calculateLayout()
+    val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
+    val layout =
+        calculateLayout(
+            isSideBySideSupported = isSideBySideSupported,
+        )
 
     Box(modifier) {
         Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
@@ -567,6 +571,11 @@
 /**
  * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
  * anywhere on the background to flip their positions.
+ *
+ * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
+ * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
+ * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
+ * rendering of the bouncer will be used instead of the side-by-side layout.
  */
 @Composable
 private fun SideBySide(
@@ -628,7 +637,9 @@
 }
 
 @Composable
-private fun calculateLayout(): Layout {
+private fun calculateLayout(
+    isSideBySideSupported: Boolean,
+): Layout {
     val windowSizeClass = LocalWindowSizeClass.current
     val width = windowSizeClass.widthSizeClass
     val height = windowSizeClass.heightSizeClass
@@ -657,7 +668,7 @@
         // Large and tall devices (i.e. tablet in portrait).
         isTall -> Layout.STACKED
         // Large and wide/square devices (i.e. tablet in landscape, unfolded).
-        else -> Layout.SIDE_BY_SIDE
+        else -> if (isSideBySideSupported) Layout.SIDE_BY_SIDE else Layout.STANDARD
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index b2a7607..7265c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -29,14 +29,15 @@
 class BouncerRepository
 @Inject
 constructor(
-    flags: FeatureFlagsClassic,
+    private val flags: FeatureFlagsClassic,
 ) {
     private val _message = MutableStateFlow<String?>(null)
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<String?> = _message.asStateFlow()
 
     /** Whether the user switcher should be displayed within the bouncer UI on large screens. */
-    val isUserSwitcherVisible: Boolean = flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
+    val isUserSwitcherVisible: Boolean
+        get() = flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
 
     fun setMessage(message: String?) {
         _message.value = message
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 4ce1422..b9a913e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -97,7 +97,8 @@
     val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
     /** Whether the user switcher should be displayed within the bouncer UI on large screens. */
-    val isUserSwitcherVisible: Boolean = repository.isUserSwitcherVisible
+    val isUserSwitcherVisible: Boolean
+        get() = repository.isUserSwitcherVisible
 
     init {
         if (flags.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 0f77724..73d15f0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -100,7 +100,8 @@
                 initialValue = emptyList(),
             )
 
-    val isUserSwitcherVisible: Boolean = bouncerInteractor.isUserSwitcherVisible
+    val isUserSwitcherVisible: Boolean
+        get() = bouncerInteractor.isUserSwitcherVisible
 
     private val isInputEnabled: StateFlow<Boolean> =
         bouncerInteractor.isThrottled
@@ -162,6 +163,23 @@
             initialValue = null
         )
 
+    /**
+     * Whether the "side-by-side" layout is supported.
+     *
+     * When presented on its own, without a user switcher (e.g. not on communal devices like
+     * tablets, for example), some authentication method UIs don't do well if they're shown in the
+     * side-by-side layout; these need to be shown with the standard layout so they can take up as
+     * much width as possible.
+     */
+    val isSideBySideSupported: StateFlow<Boolean> =
+        authMethodViewModel
+            .map { authMethod -> isSideBySideSupported(authMethod) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = isSideBySideSupported(authMethodViewModel.value),
+            )
+
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
@@ -190,6 +208,10 @@
         _throttlingDialogMessage.value = null
     }
 
+    private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
+        return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
+    }
+
     private fun toMessageViewModel(
         message: String?,
         isThrottled: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index c159b66..6ef518e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -20,9 +20,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -202,6 +204,28 @@
             assertThat(throttlingDialogMessage).isNull()
         }
 
+    @Test
+    fun isSideBySideSupported() =
+        testScope.runTest {
+            val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
+            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(isSideBySideSupported).isTrue()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+            assertThat(isSideBySideSupported).isTrue()
+
+            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(isSideBySideSupported).isTrue()
+
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+            assertThat(isSideBySideSupported).isFalse()
+        }
+
     private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> {
         return listOf(
             DomainLayerAuthenticationMethodModel.None,