From 3e388b266f326928631818d4beab486186f36150 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Thu, 17 Oct 2024 13:47:03 -0700 Subject: [flexiglass] Fixes long-press on emergency button. Note: the long-press still doesn't do anything because the code in the BouncerActionButtonInteractor code path for long-press checks EmergencyAffordanceManager.needsEmergencyAffordance that returns false but I made sure that the long-press action _is_ triggered. As it turns out, adding a combinedClickable to a Material Button doesn't work because the Material Button already sets clickable on its Surface which prevents the touch from ever getting to the combinedClickable. Unfortunately, rolling my own simplified button was the only path forward. Fix: 369767936 Test: manually verified that a single click still opens the emergency call activity Test: manually verified that long-press triggers the right code path; even if it's not currently working (it also doesn't work in pre-flexiglass/legacy) Flag: com.android.systemui.scene_container Change-Id: I1e684ba2df012c0416b0f50723a82fbd9cb3b8f9 --- .../bouncer/ui/composable/BouncerContent.kt | 226 +++++++-------------- .../interactor/BouncerActionButtonInteractor.kt | 2 - 2 files changed, 72 insertions(+), 156 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 7dc2901273b2..c1c3b1fb6c5a 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 @@ -28,6 +28,7 @@ import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement @@ -46,7 +47,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -64,6 +64,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.onKeyEvent @@ -72,6 +73,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset @@ -88,7 +92,6 @@ import com.android.compose.animation.scene.SceneKey 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.BouncerDialogFactory @@ -131,7 +134,7 @@ fun BouncerContent( layout: BouncerSceneLayout, viewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, - modifier: Modifier + modifier: Modifier, ) { Box( // Allows the content within each of the layouts to react to the appearance and @@ -140,31 +143,17 @@ fun BouncerContent( // 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().onKeyEvent(viewModel::onKeyEvent), + modifier = modifier.imePadding().onKeyEvent(viewModel::onKeyEvent) ) { when (layout) { - BouncerSceneLayout.STANDARD_BOUNCER -> - StandardLayout( - viewModel = viewModel, - ) + 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, - ) + BesideUserSwitcherLayout(viewModel = viewModel) + BouncerSceneLayout.BELOW_USER_SWITCHER -> BelowUserSwitcherLayout(viewModel = viewModel) + BouncerSceneLayout.SPLIT_BOUNCER -> SplitLayout(viewModel = viewModel) } - Dialog( - bouncerViewModel = viewModel, - dialogFactory = dialogFactory, - ) + Dialog(bouncerViewModel = viewModel, dialogFactory = dialogFactory) } } @@ -173,31 +162,19 @@ fun BouncerContent( * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.). */ @Composable -private fun StandardLayout( - viewModel: BouncerSceneContentViewModel, - modifier: Modifier = Modifier, -) { +private fun StandardLayout(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) { val isHeightExpanded = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded FoldAware( - modifier = - modifier.padding( - start = 32.dp, - top = 92.dp, - end = 32.dp, - bottom = 48.dp, - ), + modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 48.dp), viewModel = viewModel, aboveFold = { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth(), ) { - StatusMessage( - viewModel = viewModel.message, - modifier = Modifier, - ) + StatusMessage(viewModel = viewModel.message, modifier = Modifier) OutputArea( viewModel = viewModel, @@ -210,9 +187,7 @@ private fun StandardLayout( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth(), ) { - Box( - modifier = Modifier.weight(1f), - ) { + Box(modifier = Modifier.weight(1f)) { InputArea( viewModel = viewModel, pinButtonRowVerticalSpacing = 12.dp, @@ -221,10 +196,7 @@ private fun StandardLayout( ) } - ActionArea( - viewModel = viewModel, - modifier = Modifier.padding(top = 48.dp), - ) + ActionArea(viewModel = viewModel, modifier = Modifier.padding(top = 48.dp)) } }, ) @@ -235,10 +207,7 @@ private fun StandardLayout( * by double-tapping on the side. */ @Composable -private fun SplitLayout( - viewModel: BouncerSceneContentViewModel, - modifier: Modifier = Modifier, -) { +private fun SplitLayout(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) { val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle() Row( @@ -248,12 +217,10 @@ private fun SplitLayout( .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), - ) { + Box(modifier = Modifier.fillMaxHeight().weight(1f)) { when (authMethod) { is PinBouncerViewModel -> { StatusMessage( @@ -263,7 +230,7 @@ private fun SplitLayout( OutputArea( viewModel = viewModel, modifier = - Modifier.align(Alignment.Center).sysuiResTag("bouncer_text_entry") + Modifier.align(Alignment.Center).sysuiResTag("bouncer_text_entry"), ) ActionArea( @@ -293,9 +260,7 @@ private fun SplitLayout( } // Right side (in right-to-left locales). - Box( - modifier = Modifier.fillMaxHeight().weight(1f), - ) { + Box(modifier = Modifier.fillMaxHeight().weight(1f)) { when (authMethod) { is PinBouncerViewModel, is PatternBouncerViewModel -> { @@ -311,13 +276,11 @@ private fun SplitLayout( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth().align(Alignment.Center), ) { - StatusMessage( - viewModel = viewModel.message, - ) + StatusMessage(viewModel = viewModel.message) OutputArea( viewModel = viewModel, modifier = - Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry") + Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry"), ) } } @@ -365,7 +328,7 @@ private fun BesideUserSwitcherLayout( .padding( top = if (isHeightExpanded) 128.dp else 96.dp, bottom = if (isHeightExpanded) 128.dp else 48.dp, - ), + ) ) { LaunchedEffect(isSwapped) { swapAnimationEnd = false } val animatedOffset by @@ -419,14 +382,12 @@ private fun BesideUserSwitcherLayout( aboveFold = { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) { - StatusMessage( - viewModel = viewModel.message, - ) + StatusMessage(viewModel = viewModel.message) OutputArea( viewModel = viewModel, - modifier = Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry") + modifier = Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry"), ) } }, @@ -444,7 +405,7 @@ private fun BesideUserSwitcherLayout( Box( modifier = Modifier.weight(1f) - .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp), + .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp) ) { InputArea( viewModel = viewModel, @@ -470,16 +431,8 @@ private fun BelowUserSwitcherLayout( viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { - Column( - modifier = - modifier.padding( - vertical = 128.dp, - ) - ) { - UserSwitcher( - viewModel = viewModel, - modifier = Modifier.fillMaxWidth(), - ) + Column(modifier = modifier.padding(vertical = 128.dp)) { + UserSwitcher(viewModel = viewModel, modifier = Modifier.fillMaxWidth()) Spacer(Modifier.weight(1f)) @@ -488,9 +441,7 @@ private fun BelowUserSwitcherLayout( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth(), ) { - StatusMessage( - viewModel = viewModel.message, - ) + StatusMessage(viewModel = viewModel.message) OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) InputArea( @@ -500,10 +451,7 @@ private fun BelowUserSwitcherLayout( modifier = Modifier.padding(top = 128.dp), ) - ActionArea( - viewModel = viewModel, - modifier = Modifier.padding(top = 48.dp), - ) + ActionArea(viewModel = viewModel, modifier = Modifier.padding(top = 48.dp)) } } } @@ -533,19 +481,11 @@ private fun FoldAware( SceneTransitionLayout(state, modifier = modifier) { scene(SceneKeys.ContiguousSceneKey) { - FoldableScene( - aboveFold = aboveFold, - belowFold = belowFold, - isSplit = false, - ) + FoldableScene(aboveFold = aboveFold, belowFold = belowFold, isSplit = false) } scene(SceneKeys.SplitSceneKey) { - FoldableScene( - aboveFold = aboveFold, - belowFold = belowFold, - isSplit = true, - ) + FoldableScene(aboveFold = aboveFold, belowFold = belowFold, isSplit = true) } } } @@ -562,9 +502,7 @@ private fun SceneScope.FoldableScene( R.dimen.motion_layout_half_fold_bouncer_height_ratio ) - Column( - modifier = modifier.fillMaxHeight(), - ) { + Column(modifier = modifier.fillMaxHeight()) { // Content above the fold, when split on a foldable device in a "table top" posture: Box( modifier = @@ -575,7 +513,7 @@ private fun SceneScope.FoldableScene( } else { Modifier } - ), + ) ) { aboveFold() } @@ -590,7 +528,7 @@ private fun SceneScope.FoldableScene( } else { 1f } - ), + ) ) { belowFold() } @@ -598,10 +536,7 @@ private fun SceneScope.FoldableScene( } @Composable -private fun StatusMessage( - viewModel: BouncerMessageViewModel, - modifier: Modifier = Modifier, -) { +private fun StatusMessage(viewModel: BouncerMessageViewModel, modifier: Modifier = Modifier) { val message: MessageViewModel? by viewModel.message.collectAsStateWithLifecycle() DisposableEffect(Unit) { @@ -634,7 +569,7 @@ private fun StatusMessage( fontSize = 14.sp, lineHeight = 20.sp, overflow = TextOverflow.Ellipsis, - maxLines = 2 + maxLines = 2, ) } } @@ -647,22 +582,19 @@ private fun StatusMessage( * For example, this can be the PIN shapes or password text field. */ @Composable -private fun OutputArea( - viewModel: BouncerSceneContentViewModel, - modifier: Modifier = Modifier, -) { +private fun OutputArea(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) { val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethodViewModel.collectAsStateWithLifecycle() when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> PinInputDisplay( viewModel = nonNullViewModel, - modifier = modifier.sysuiResTag("bouncer_text_entry") + modifier = modifier.sysuiResTag("bouncer_text_entry"), ) is PasswordBouncerViewModel -> PasswordBouncer( viewModel = nonNullViewModel, - modifier = modifier.sysuiResTag("bouncer_text_entry") + modifier = modifier.sysuiResTag("bouncer_text_entry"), ) else -> Unit } @@ -703,10 +635,7 @@ private fun InputArea( } @Composable -private fun ActionArea( - viewModel: BouncerSceneContentViewModel, - modifier: Modifier = Modifier, -) { +private fun ActionArea(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) { val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsStateWithLifecycle() val appearFadeInAnimatable = remember { Animatable(0f) } @@ -722,7 +651,7 @@ private fun ActionArea( durationMillis = 450, delayMillis = 133, easing = Easings.LegacyDecelerate, - ) + ), ) } LaunchedEffect(Unit) { @@ -733,39 +662,35 @@ private fun ActionArea( durationMillis = 450, delayMillis = 133, easing = Easings.StandardDecelerate, - ) + ), ) } Box( modifier = - modifier.graphicsLayer { - // Translate the button up from an initially pushed-down position: - translationY = (1 - appearMoveAnimatable.value) * appearAnimationInitialOffset - // Fade the button in: - alpha = appearFadeInAnimatable.value - }, + modifier + .graphicsLayer { + // Translate the button up from an initially pushed-down position: + translationY = + (1 - appearMoveAnimatable.value) * appearAnimationInitialOffset + // Fade the button in: + alpha = appearFadeInAnimatable.value + } + .height(56.dp) + .clip(ButtonDefaults.shape) + .background(color = MaterialTheme.colorScheme.tertiaryContainer) + .semantics { role = Role.Button } + .combinedClickable( + onClick = { actionButtonViewModel.onClick() }, + onLongClick = actionButtonViewModel.onLongClick?.let { { it.invoke() } }, + ) ) { - 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, - ) - } + Text( + text = actionButtonViewModel.label, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onTertiaryContainer, + modifier = Modifier.align(Alignment.Center).padding(ButtonDefaults.ContentPadding), + ) } } } @@ -800,15 +725,10 @@ private fun Dialog( /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */ @Composable -private fun UserSwitcher( - viewModel: BouncerSceneContentViewModel, - modifier: Modifier = Modifier, -) { +private fun UserSwitcher(viewModel: BouncerSceneContentViewModel, 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, - ) + Box(modifier = modifier) return } @@ -891,7 +811,7 @@ private fun UserSwitcherDropdownMenu( MaterialTheme( colorScheme = MaterialTheme.colorScheme.copy( - surface = MaterialTheme.colorScheme.surfaceContainerHighest, + surface = MaterialTheme.colorScheme.surfaceContainerHighest ), shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)), ) { @@ -932,9 +852,7 @@ private fun UserSwitcherDropdownMenu( * 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. */ -private fun animatedAlpha( - offset: Float, -): Float { +private fun animatedAlpha(offset: Float): Float { // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs // between x = 0 and x = 1. diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt index 2c026c0bb5ce..8d8580fc2c8c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt @@ -119,8 +119,6 @@ constructor( onLongClick = { if (emergencyAffordanceManager.needsEmergencyAffordance()) { prepareToPerformAction() - - // TODO(b/369767936): Check that !longPressWasDragged before invoking. emergencyAffordanceManager.performEmergencyCall() } }, -- cgit v1.2.3-59-g8ed1b