diff options
11 files changed, 616 insertions, 538 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 87c28862f6f6..47f5663f25a7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationApi::class) + package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog @@ -29,18 +31,18 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown @@ -51,6 +53,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -78,6 +81,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf +import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel @@ -100,37 +104,41 @@ fun BouncerContent( dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { - val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) - when (layout) { - BouncerSceneLayout.STANDARD -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = modifier, - ) - BouncerSceneLayout.SIDE_BY_SIDE -> - SideBySideLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = modifier, - ) - BouncerSceneLayout.STACKED -> - StackedLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = modifier, - ) - BouncerSceneLayout.SPLIT -> - SplitLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = modifier, - ) + Box( + // Allows the content within each of the layouts to react to the appearance and + // disappearance of the IME, which is also known as the software keyboard. + // + // Despite the keyboard only being part of the password bouncer, adding it at this level is + // both necessary to properly handle the keyboard in all layouts and harmless in cases when + // the keyboard isn't used (like the PIN or pattern auth methods). + modifier = modifier.imePadding(), + ) { + when (layout) { + BouncerSceneLayout.STANDARD_BOUNCER -> + StandardLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.BESIDE_USER_SWITCHER -> + BesideUserSwitcherLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.BELOW_USER_SWITCHER -> + BelowUserSwitcherLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.SPLIT_BOUNCER -> + SplitLayout( + viewModel = viewModel, + ) + } + + Dialog( + viewModel = viewModel, + dialogFactory = dialogFactory, + ) } } @@ -141,15 +149,333 @@ fun BouncerContent( @Composable private fun StandardLayout( viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, - layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD, - outputOnly: Boolean = false, +) { + val isHeightExpanded = + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded + + FoldAware( + modifier = + modifier.padding( + top = 92.dp, + bottom = 48.dp, + ), + viewModel = viewModel, + aboveFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + StatusMessage( + viewModel = viewModel, + modifier = Modifier, + ) + + OutputArea( + viewModel = viewModel, + modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp), + ) + } + }, + belowFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + Box( + modifier = Modifier.weight(1f), + ) { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = false, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + }, + ) +} + +/** + * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable + * by double-tapping on the side. + */ +@Composable +private fun SplitLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val authMethod by viewModel.authMethodViewModel.collectAsState() + + Row( + modifier = + modifier + .fillMaxHeight() + .padding( + horizontal = 24.dp, + vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp, + ), + ) { + // Left side (in left-to-right locales). + Box( + modifier = Modifier.fillMaxHeight().weight(1f), + ) { + when (authMethod) { + is PinBouncerViewModel -> { + StatusMessage( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center)) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp), + ) + } + is PatternBouncerViewModel -> { + StatusMessage( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp), + ) + } + is PasswordBouncerViewModel -> { + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + else -> Unit + } + } + + // Right side (in right-to-left locales). + Box( + modifier = Modifier.fillMaxHeight().weight(1f), + ) { + when (authMethod) { + is PinBouncerViewModel, + is PatternBouncerViewModel -> { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 8.dp, + centerPatternDotsVertically = true, + modifier = Modifier.align(Alignment.Center), + ) + } + is PasswordBouncerViewModel -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth().align(Alignment.Center), + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + } + } + else -> Unit + } + } + } +} + +/** + * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap + * anywhere on the background to flip their positions. + */ +@Composable +private fun BesideUserSwitcherLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val layoutDirection = LocalLayoutDirection.current + val isLeftToRight = layoutDirection == LayoutDirection.Ltr + val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } + val isHeightExpanded = + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded + val authMethod by viewModel.authMethodViewModel.collectAsState() + + Row( + modifier = + modifier.pointerInput(Unit) { + detectTapGestures( + onDoubleTap = { offset -> + // Depending on where the user double tapped, switch the elements such that + // the endContent is closer to the side that was double tapped. + setSwapped(offset.x < size.width / 2) + } + ) + }, + ) { + val animatedOffset by + animateFloatAsState( + targetValue = + if (!isSwapped) { + // A non-swapped element has its natural placement so it's not offset. + 0f + } else if (isLeftToRight) { + // A swapped element has its elements offset horizontally. In the case of + // LTR locales, this means pushing the element to the right, hence the + // positive number. + 1f + } else { + // A swapped element has its elements offset horizontally. In the case of + // RTL locales, this means pushing the element to the left, hence the + // negative number. + -1f + }, + label = "offset", + ) + + fun Modifier.swappable(inversed: Boolean = false): Modifier { + return graphicsLayer { + translationX = + size.width * + animatedOffset * + if (inversed) { + // A negative sign is used to make sure this is offset in the direction + // that's opposite to the direction that the user switcher is pushed in. + -1 + } else { + 1 + } + alpha = animatedAlpha(animatedOffset) + } + } + + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.weight(1f).align(Alignment.CenterVertically).swappable(), + ) + + FoldAware( + modifier = + Modifier.weight(1f) + .padding( + if (isHeightExpanded) { + PaddingValues(vertical = 128.dp) + } else { + PaddingValues(top = 94.dp, bottom = 48.dp) + } + ) + .swappable(inversed = true), + viewModel = viewModel, + aboveFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + } + }, + belowFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + val isOutputAreaVisible = authMethod !is PatternBouncerViewModel + // If there is an output area and the window is not tall enough, spacing needs + // to be added between the input and the output areas (otherwise the two get + // very squished together). + val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded + + Box( + modifier = + Modifier.weight(1f) + .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp), + ) { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = true, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + }, + ) + } +} + +/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ +@Composable +private fun BelowUserSwitcherLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier.padding( + vertical = 128.dp, + ) + ) { + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = true, + modifier = Modifier.padding(top = 128.dp), + ) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + } + } +} + +@Composable +private fun FoldAware( + viewModel: BouncerViewModel, + aboveFold: @Composable BoxScope.() -> Unit, + belowFold: @Composable BoxScope.() -> Unit, + modifier: Modifier = Modifier, ) { val foldPosture: FoldPosture by foldPosture() val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() - val isSplitAroundTheFold = - foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired + val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired val currentSceneKey = if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey @@ -160,115 +486,57 @@ private fun StandardLayout( modifier = modifier, ) { scene(SceneKeys.ContiguousSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = layout, - outputOnly = outputOnly, + FoldableScene( + aboveFold = aboveFold, + belowFold = belowFold, isSplit = false, ) } scene(SceneKeys.SplitSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = layout, - outputOnly = outputOnly, + FoldableScene( + aboveFold = aboveFold, + belowFold = belowFold, isSplit = true, ) } } } -/** - * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user - * switcher UI) and laid out vertically, centered horizontally. - * - * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't - * render across the location of the fold hardware when the device is fully or part-way unfolded - * with the fold hinge in a horizontal position. - * - * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN - * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter - * their PIN or pattern. - */ @Composable -private fun SceneScope.FoldSplittable( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - layout: BouncerSceneLayout, - outputOnly: Boolean, +private fun SceneScope.FoldableScene( + aboveFold: @Composable BoxScope.() -> Unit, + belowFold: @Composable BoxScope.() -> Unit, isSplit: Boolean, modifier: Modifier = Modifier, ) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.dialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() val splitRatio = LocalContext.current.resources.getFloat( R.dimen.motion_layout_half_fold_bouncer_height_ratio ) - Column(modifier = modifier.padding(horizontal = 32.dp)) { + Column( + modifier = modifier.fillMaxHeight(), + ) { // Content above the fold, when split on a foldable device in a "table top" posture: Box( modifier = Modifier.element(SceneElements.AboveFold) - .fillMaxWidth() .then( if (isSplit) { Modifier.weight(splitRatio) - } else if (outputOnly) { - Modifier.fillMaxHeight() } else { Modifier } ), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding), - ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - ) - } - - if (!outputOnly) { - Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput)) - - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - layout = layout, - ) - } - } - - if (outputOnly) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - layout = layout, - modifier = Modifier.align(Alignment.Center), - ) - } + aboveFold() } // Content below the fold, when split on a foldable device in a "table top" posture: Box( modifier = Modifier.element(SceneElements.BelowFold) - .fillMaxWidth() .weight( if (isSplit) { 1 - splitRatio @@ -277,73 +545,40 @@ private fun SceneScope.FoldSplittable( } ), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize() - ) { - if (!outputOnly) { - Box(Modifier.weight(1f)) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - layout = layout, - modifier = Modifier.align(Alignment.BottomCenter), - ) - } - } - - Spacer(Modifier.height(48.dp)) - - val actionButtonModifier = Modifier.height(56.dp) - - actionButton.let { actionButtonViewModel -> - if (actionButtonViewModel != null) { - BouncerActionButton( - viewModel = actionButtonViewModel, - modifier = actionButtonModifier, - ) - } else { - Spacer(modifier = actionButtonModifier) - } - } - - Spacer(Modifier.height(layout.bottomPadding)) - } + belowFold() } + } +} - if (dialogMessage != null) { - if (dialog == null) { - dialog = - dialogFactory().apply { - setMessage(dialogMessage) - setButton( - DialogInterface.BUTTON_NEUTRAL, - context.getString(R.string.ok), - ) { _, _ -> - viewModel.onDialogDismissed() - } - setCancelable(false) - setCanceledOnTouchOutside(false) - show() - } - } - } else { - dialog?.dismiss() - dialog = null - } +@Composable +private fun StatusMessage( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + modifier = modifier, + ) { + Text( + text = it.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) } } /** - * Renders the user input area, where the user interacts with the UI to enter their credentials. + * Renders the user output area, where the user sees what they entered. * - * For example, this can be the pattern input area, the password text box, or pin pad. + * For example, this can be the PIN shapes or password text field. */ @Composable -private fun UserInputArea( +private fun OutputArea( viewModel: BouncerViewModel, - visibility: UserInputAreaVisibility, - layout: BouncerSceneLayout, modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by @@ -351,66 +586,115 @@ private fun UserInputArea( when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.OUTPUT_ONLY -> - PinInputDisplay( - viewModel = nonNullViewModel, - modifier = modifier, - ) - UserInputAreaVisibility.INPUT_ONLY -> - PinPad( - viewModel = nonNullViewModel, - layout = layout, - modifier = modifier, - ) - } + PinInputDisplay( + viewModel = nonNullViewModel, + modifier = modifier, + ) is PasswordBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = modifier, - ) - } - is PatternBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PatternBouncer( - viewModel = nonNullViewModel, - layout = layout, - modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false), - ) - } + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = modifier, + ) else -> Unit } } /** - * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call. + * Renders the user input area, where the user enters their credentials. + * + * For example, this can be the pattern input area or the PIN pad. */ -@OptIn(ExperimentalFoundationApi::class) @Composable -private fun BouncerActionButton( - viewModel: BouncerActionButtonModel, +private fun InputArea( + viewModel: BouncerViewModel, + pinButtonRowVerticalSpacing: Dp, + centerPatternDotsVertically: Boolean, modifier: Modifier = Modifier, ) { - Button( - onClick = viewModel.onClick, - modifier = - modifier.thenIf(viewModel.onLongClick != null) { - Modifier.combinedClickable( - onClick = viewModel.onClick, - onLongClick = viewModel.onLongClick, + val authMethodViewModel: AuthMethodBouncerViewModel? by + viewModel.authMethodViewModel.collectAsState() + + when (val nonNullViewModel = authMethodViewModel) { + is PinBouncerViewModel -> { + PinPad( + viewModel = nonNullViewModel, + verticalSpacing = pinButtonRowVerticalSpacing, + modifier = modifier, + ) + } + is PatternBouncerViewModel -> { + PatternBouncer( + viewModel = nonNullViewModel, + centerDotsVertically = centerPatternDotsVertically, + modifier = modifier, + ) + } + else -> Unit + } +} + +@Composable +private fun ActionArea( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + + actionButton?.let { actionButtonViewModel -> + Box( + modifier = modifier, + ) { + Button( + onClick = actionButtonViewModel.onClick, + modifier = + Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) { + Modifier.combinedClickable( + onClick = actionButtonViewModel.onClick, + onLongClick = actionButtonViewModel.onLongClick, + ) + }, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ), + ) { + Text( + text = actionButtonViewModel.label, + style = MaterialTheme.typography.bodyMedium, ) - }, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) { - Text( - text = viewModel.label, - style = MaterialTheme.typography.bodyMedium, - ) + } + } + } +} + +@Composable +private fun Dialog( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, +) { + val dialogMessage: String? by viewModel.dialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null } } @@ -420,6 +704,14 @@ private fun UserSwitcher( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { + if (!viewModel.isUserSwitcherVisible) { + // Take up the same space as the user switcher normally would, but with nothing inside it. + Box( + modifier = modifier, + ) + return + } + val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) @@ -539,195 +831,10 @@ private fun UserSwitcherDropdownMenu( } } -/** - * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable - * by double-tapping on the side. - */ -@Composable -private fun SplitLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.SPLIT, - outputOnly = true, - modifier = startContentModifier, - ) - }, - endContent = { endContentModifier -> - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - layout = BouncerSceneLayout.SPLIT, - modifier = endContentModifier, - ) - }, - layout = BouncerSceneLayout.SPLIT, - modifier = modifier, - ) -} - -/** - * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background - * to flip their positions. - */ -@Composable -private fun SwappableLayout( - startContent: @Composable (Modifier) -> Unit, - endContent: @Composable (Modifier) -> Unit, - layout: BouncerSceneLayout, - modifier: Modifier = Modifier, -) { - val layoutDirection = LocalLayoutDirection.current - val isLeftToRight = layoutDirection == LayoutDirection.Ltr - val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } - - Row( - modifier = - modifier.pointerInput(Unit) { - detectTapGestures( - onDoubleTap = { offset -> - // Depending on where the user double tapped, switch the elements such that - // the endContent is closer to the side that was double tapped. - setSwapped(offset.x < size.width / 2) - } - ) - }, - ) { - val animatedOffset by - animateFloatAsState( - targetValue = - if (!isSwapped) { - // When startContent is first, both elements have their natural placement so - // they are not offset in any way. - 0f - } else if (isLeftToRight) { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of LTR locales, this means pushing startContent - // to the right, hence the positive number. - 1f - } else { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of RTL locales, this means pushing startContent - // to the left, hence the negative number. - -1f - }, - label = "offset", - ) - - startContent( - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - translationX = size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) - - Box( - modifier = - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - // A negative sign is used to make sure this is offset in the direction that's - // opposite of the direction that the user switcher is pushed in. - translationX = -size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) { - endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp)) - } - } -} - -/** - * 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 SideBySideLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = startContentModifier, - ) - } else { - Box( - modifier = startContentModifier, - ) - } - }, - endContent = { endContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.SIDE_BY_SIDE, - modifier = endContentModifier, - ) - }, - layout = BouncerSceneLayout.SIDE_BY_SIDE, - modifier = modifier, - ) -} - -/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ -@Composable -private fun StackedLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - ) { - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } - - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.STACKED, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } -} - interface BouncerDialogFactory { operator fun invoke(): AlertDialog } -/** Enumerates all supported user-input area visibilities. */ -private enum class UserInputAreaVisibility { - /** - * Only the area where the user enters the input is shown; the area where the input is reflected - * back to the user is not shown. - */ - INPUT_ONLY, - /** - * Only the area where the input is reflected back to the user is shown; the area where the - * input is entered by the user is not shown. - */ - OUTPUT_ONLY, -} - /** * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of * the two reaches a stopping point but `0` in the middle of the transition. @@ -774,48 +881,3 @@ private object SceneElements { private val SceneTransitions = transitions { from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } } - -/** Whether a more compact size should be used for various spacing dimensions. */ -internal val BouncerSceneLayout.isUseCompactSize: Boolean - get() = - when (this) { - BouncerSceneLayout.SIDE_BY_SIDE -> true - BouncerSceneLayout.SPLIT -> true - else -> false - } - -/** Amount of space to place between the message and the entered input UI elements, in dips. */ -private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp - get() = - when { - this == BouncerSceneLayout.STACKED -> 24.dp - isUseCompactSize -> 96.dp - else -> 128.dp - } - -/** Amount of space to place above the topmost UI element, in dips. */ -private val BouncerSceneLayout.topPadding: Dp - get() = - if (this == BouncerSceneLayout.SPLIT) { - 40.dp - } else { - 92.dp - } - -/** Amount of space to place below the bottommost UI element, in dips. */ -private val BouncerSceneLayout.bottomPadding: Dp - get() = - if (this == BouncerSceneLayout.SPLIT) { - 40.dp - } else { - 48.dp - } - -/** The in-a-box alignment for the content on the "end" side of a swappable layout. */ -private val BouncerSceneLayout.swappableEndContentAlignment: Alignment - get() = - if (this == BouncerSceneLayout.SPLIT) { - Alignment.Center - } else { - Alignment.BottomCenter - } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt index 08b7559dcf97..1c3d93c3b7e9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt @@ -26,8 +26,8 @@ import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal /** * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If - * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by - * [BouncerSceneLayout.STANDARD]. + * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by + * [BouncerSceneLayout.STANDARD_BOUNCER]. */ @Composable fun calculateLayout( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 279995959da2..09608115c456 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -17,7 +17,6 @@ package com.android.systemui.bouncer.ui.composable import android.view.ViewTreeObserver -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.LocalTextStyle @@ -31,7 +30,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.FocusRequester @@ -81,42 +79,38 @@ internal fun PasswordBouncer( } } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier, - ) { - val color = MaterialTheme.colorScheme.onSurfaceVariant - val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } + val color = MaterialTheme.colorScheme.onSurfaceVariant + val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } - TextField( - value = password, - onValueChange = viewModel::onPasswordInputChanged, - enabled = isInputEnabled, - visualTransformation = PasswordVisualTransformation(), - singleLine = true, - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions( - onDone = { viewModel.onAuthenticateKeyPressed() }, - ), - modifier = - Modifier.focusRequester(focusRequester) - .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } - .drawBehind { - drawLine( - color = color, - start = Offset(x = 0f, y = size.height - lineWidthPx), - end = Offset(size.width, y = size.height - lineWidthPx), - strokeWidth = lineWidthPx, - ) - }, - ) - } + TextField( + value = password, + onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + keyboardOptions = + KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions( + onDone = { viewModel.onAuthenticateKeyPressed() }, + ), + modifier = + modifier + .focusRequester(focusRequester) + .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } + .drawBehind { + drawLine( + color = color, + start = Offset(x = 0f, y = size.height - lineWidthPx), + end = Offset(size.width, y = size.height - lineWidthPx), + strokeWidth = lineWidthPx, + ) + }, + ) } /** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */ diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index a4b195508b3d..0a5f5d281f83 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -24,6 +24,8 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -48,7 +50,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.modifiers.thenIf import com.android.internal.R -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel import kotlin.math.min @@ -61,11 +62,14 @@ import kotlinx.coroutines.launch * UI for the input part of a pattern-requiring version of the bouncer. * * The user can press, hold, and drag their pointer to select dots along a grid of dots. + * + * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if + * `false`, the dots will be pushed towards the end/bottom of the axis. */ @Composable internal fun PatternBouncer( viewModel: PatternBouncerViewModel, - layout: BouncerSceneLayout, + centerDotsVertically: Boolean, modifier: Modifier = Modifier, ) { DisposableEffect(Unit) { @@ -197,6 +201,14 @@ internal fun PatternBouncer( Canvas( modifier + // Because the width also includes spacing to the left and right of the leftmost and + // rightmost dots in the grid and because UX mocks specify the width without that + // spacing, the actual width needs to be defined slightly bigger than the UX mock width. + .width((262 * colCount / 2).dp) + // Because the height also includes spacing above and below the topmost and bottommost + // dots in the grid and because UX mocks specify the height without that spacing, the + // actual height needs to be defined slightly bigger than the UX mock height. + .height((262 * rowCount / 2).dp) // Need to clip to bounds to make sure that the lines don't follow the input pointer // when it leaves the bounds of the dot grid. .clipToBounds() @@ -260,7 +272,7 @@ internal fun PatternBouncer( availableSize = containerSize.height, spacingPerDot = spacing, dotCount = rowCount, - isCentered = layout.isCenteredVertically, + isCentered = centerDotsVertically, ) offset = Offset(horizontalOffset, verticalOffset) scale = (colCount * spacing) / containerSize.width @@ -423,10 +435,6 @@ private fun offset( } } -/** Whether the UI should be centered vertically. */ -private val BouncerSceneLayout.isCenteredVertically: Boolean - get() = this == BouncerSceneLayout.SPLIT - private const val DOT_DIAMETER_DP = 16 private const val SELECTED_DOT_DIAMETER_DP = 24 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 8f5d9f4a1790..f505b9067140 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.compose.modifiers.thenIf -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription @@ -70,7 +69,7 @@ import kotlinx.coroutines.launch @Composable fun PinPad( viewModel: PinBouncerViewModel, - layout: BouncerSceneLayout, + verticalSpacing: Dp, modifier: Modifier = Modifier, ) { DisposableEffect(Unit) { @@ -96,8 +95,8 @@ fun PinPad( VerticalGrid( columns = columns, - verticalSpacing = layout.verticalSpacing, - horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth), + verticalSpacing = verticalSpacing, + horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp), modifier = modifier, ) { repeat(9) { index -> @@ -355,14 +354,6 @@ private fun calculateHorizontalSpacingBetweenColumns( return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1) } -/** The width of the grid of PIN pad buttons, in dips. */ -private val BouncerSceneLayout.gridWidth: Dp - get() = if (isUseCompactSize) 292.dp else 300.dp - -/** The spacing between rows of PIN pad buttons, in dips. */ -private val BouncerSceneLayout.verticalSpacing: Dp - get() = if (isUseCompactSize) 8.dp else 12.dp - /** Number of columns in the PIN pad grid. */ private const val columns = 3 /** Maximum size (width and height) of each PIN pad button. */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt index 5385442092b9..7f97718cb623 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt @@ -21,13 +21,13 @@ import androidx.annotation.VisibleForTesting /** Enumerates all known adaptive layout configurations. */ enum class BouncerSceneLayout { /** The default UI with the bouncer laid out normally. */ - STANDARD, + STANDARD_BOUNCER, /** The bouncer is displayed vertically stacked with the user switcher. */ - STACKED, + BELOW_USER_SWITCHER, /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ - SIDE_BY_SIDE, + BESIDE_USER_SWITCHER, /** The bouncer is split in two with both sides shown side-by-side. */ - SPLIT, + SPLIT_BOUNCER, } /** Enumerates the supported window size classes. */ @@ -48,19 +48,19 @@ fun calculateLayoutInternal( isSideBySideSupported: Boolean, ): BouncerSceneLayout { return when (height) { - SizeClass.COMPACT -> BouncerSceneLayout.SPLIT + SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER SizeClass.MEDIUM -> when (width) { - SizeClass.COMPACT -> BouncerSceneLayout.STANDARD - SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD - SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER } SizeClass.EXPANDED -> when (width) { - SizeClass.COMPACT -> BouncerSceneLayout.STANDARD - SizeClass.MEDIUM -> BouncerSceneLayout.STACKED - SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER + SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER } - }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported } - ?: BouncerSceneLayout.STANDARD + }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported } + ?: BouncerSceneLayout.STANDARD_BOUNCER } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index e9779cd02760..5fbb60d76fbb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -59,6 +59,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -116,6 +117,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final AuthController mAuthController; private final Lazy<SelectedUserInteractor> mUserInteractor; private final Lazy<ShadeInteractor> mShadeInteractorLazy; + private final SceneContainerFlags mSceneContainerFlags; private ViewGroup mWindowRootView; private LayoutParams mLp; private boolean mHasTopUi; @@ -162,7 +164,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW Lazy<ShadeInteractor> shadeInteractorLazy, ShadeWindowLogger logger, Lazy<SelectedUserInteractor> userInteractor, - UserTracker userTracker) { + UserTracker userTracker, + SceneContainerFlags sceneContainerFlags) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; mWindowManager = windowManager; @@ -180,6 +183,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW dumpManager.registerDumpable(this); mAuthController = authController; mUserInteractor = userInteractor; + mSceneContainerFlags = sceneContainerFlags; mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() .getInteger(R.integer.config_lockScreenDisplayTimeout); @@ -287,6 +291,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + if (mSceneContainerFlags.isEnabled()) { + // This prevents the appearance and disappearance of the software keyboard (also known + // as the "IME") from scrolling/panning the window to make room for the keyboard. + // + // The scene container logic does its own adjustment and animation when the IME appears + // or disappears. + mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + } + mWindowManager.addView(mWindowRootView, mLp); mLpChanged.copyFrom(mLp); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt index 395d7129bee3..ca9582240b93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.bouncer.ui.helper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER import com.google.common.truth.Truth.assertThat import java.util.Locale import org.junit.Test @@ -84,33 +84,33 @@ class BouncerSceneLayoutTest : SysuiTestCase() { listOf( Phone to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), Tablet to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = STACKED, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = BELOW_USER_SWITCHER, ), Folded to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), Unfolded to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = STANDARD, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = STANDARD_BOUNCER, ), TallerFolded to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), TallerUnfolded to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = SIDE_BY_SIDE, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = BESIDE_USER_SWITCHER, ), ) .flatMap { (device, expected) -> @@ -124,13 +124,13 @@ class BouncerSceneLayoutTest : SysuiTestCase() { ) ) - if (expected.whenNaturallyHeld == SIDE_BY_SIDE) { + if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) { add( TestCase( device = device, held = device.naturallyHeld, isSideBySideSupported = false, - expected = STANDARD, + expected = STANDARD_BOUNCER, ) ) } @@ -144,13 +144,13 @@ class BouncerSceneLayoutTest : SysuiTestCase() { ) ) - if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) { + if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) { add( TestCase( device = device, held = device.naturallyHeld.flip(), isSideBySideSupported = false, - expected = STANDARD, + expected = STANDARD_BOUNCER, ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index d246f0e49e1c..ae5f625b1c8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -97,6 +97,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.scene.FakeWindowRootViewComponent; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -214,6 +215,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock CoroutineDispatcher mDispatcher; private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; private @Mock SystemPropertiesHelper mSystemPropertiesHelper; + private @Mock SceneContainerFlags mSceneContainerFlags; private FakeFeatureFlags mFeatureFlags; private final int mDefaultUserId = 100; @@ -258,7 +260,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker); + mUserTracker, + mSceneContainerFlags); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 39739e7bb93d..5ffbe65d2c50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -75,6 +75,7 @@ import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.data.repository.FakeShadeRepository; @@ -138,6 +139,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private ShadeWindowLogger mShadeWindowLogger; @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private UserTracker mUserTracker; + @Mock private SceneContainerFlags mSceneContainerFlags; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; @@ -274,7 +276,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker) { + mUserTracker, + mSceneContainerFlags) { @Override protected boolean isDebuggable() { return false; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 8585d46fa8a5..5b9b390eea2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -119,6 +119,7 @@ import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; @@ -343,6 +344,8 @@ public class BubblesTest extends SysuiTestCase { private Icon mAppBubbleIcon; @Mock private Display mDefaultDisplay; + @Mock + private SceneContainerFlags mSceneContainerFlags; private final SceneTestUtils mUtils = new SceneTestUtils(this); private final TestScope mTestScope = mUtils.getTestScope(); @@ -503,7 +506,8 @@ public class BubblesTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker + mUserTracker, + mSceneContainerFlags ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); |