Loading packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +7 −4 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ 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.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics Loading Loading @@ -639,7 +640,7 @@ private fun ActionArea(viewModel: BouncerSceneContentViewModel, modifier: Modifi val appearMoveAnimatable = remember { Animatable(0f) } val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() } actionButton?.let { actionButtonViewModel -> actionButton?.let { actionButtonModel -> LaunchedEffect(Unit) { appearFadeInAnimatable.animateTo( targetValue = 1f, Loading Loading @@ -678,12 +679,14 @@ private fun ActionArea(viewModel: BouncerSceneContentViewModel, modifier: Modifi .background(color = MaterialTheme.colorScheme.tertiaryContainer) .semantics { role = Role.Button } .combinedClickable( onClick = { actionButtonViewModel.onClick() }, onLongClick = actionButtonViewModel.onLongClick?.let { { it.invoke() } }, onClick = { actionButton?.let { viewModel.onActionButtonClicked(it) } }, onLongClick = { actionButton?.let { viewModel.onActionButtonLongClicked(it) } }, ) ) { Text( text = actionButtonViewModel.label, text = stringResource(id = actionButtonModel.labelResId), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onTertiaryContainer, modifier = Modifier.align(Alignment.Center).padding(ButtonDefaults.ContentPadding), Loading packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +15 −14 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer Loading Loading @@ -83,7 +84,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL) overrideResource( R.bool.config_enable_emergency_call_while_sim_locked, ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED, ) whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId) whenever(emergencyAffordanceManager.needsEmergencyAffordance()) Loading Loading @@ -123,11 +124,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { kosmos.fakeTelephonyRepository.setIsInCall(true) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL) assertThat(actionButton?.onClick).isNotNull() assertThat(actionButton?.onLongClick).isNull() assertThat(actionButton?.labelResId).isEqualTo(R.string.lockscreen_return_to_call) assertThat(actionButton) .isInstanceOf(BouncerActionButtonModel.ReturnToCallButtonModel::class.java) actionButton?.onClick?.invoke() underTest.onReturnToCallButtonClicked() runCurrent() assertThat(metricsLogger.logs.size).isEqualTo(1) Loading @@ -150,11 +151,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { kosmos.fakeTelephonyRepository.setIsInCall(false) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL) assertThat(actionButton?.onClick).isNotNull() assertThat(actionButton?.onLongClick).isNotNull() assertThat(actionButton?.labelResId).isEqualTo(R.string.lockscreen_emergency_call) assertThat(actionButton) .isInstanceOf(BouncerActionButtonModel.EmergencyButtonModel::class.java) actionButton?.onClick?.invoke() underTest.onEmergencyButtonClicked() runCurrent() assertThat(metricsLogger.logs.size).isEqualTo(1) Loading @@ -167,12 +168,12 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { // ActivityStarter interface here. verify(emergencyAffordanceManager, never()).performEmergencyCall() actionButton?.onLongClick?.invoke() underTest.onEmergencyButtonLongClicked() verify(emergencyAffordanceManager).performEmergencyCall() } @Test fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() = fun noCall_insecureAuthMethodButSecureSim_emergencyCallButtonIsActionButton() = testScope.runTest { val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) Loading @@ -184,9 +185,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { runCurrent() assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL) assertThat(actionButton?.onClick).isNotNull() assertThat(actionButton?.onLongClick).isNotNull() assertThat(actionButton?.labelResId).isEqualTo(R.string.lockscreen_emergency_call) assertThat(actionButton) .isInstanceOf(BouncerActionButtonModel.EmergencyButtonModel::class.java) } @Test Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -332,7 +332,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertWithMessage("Bouncer action button not visible") .that(bouncerActionButton) .isNotNull() bouncerActionButton?.onClick?.invoke() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) runCurrent() // TODO(b/369765704): Assert that an activity was started once we use ActivityStarter. Loading @@ -354,7 +354,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertWithMessage("Bouncer action button not visible during call") .that(bouncerActionButton) .isNotNull() bouncerActionButton?.onClick?.invoke() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) runCurrent() verify(kosmos.mockTelecomManager).showInCallScreen(any()) Loading packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt +24 −31 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background Loading Loading @@ -75,7 +74,6 @@ constructor( private val metricsLogger: MetricsLogger, private val dozeLogger: DozeLogger, private val sceneInteractor: Lazy<SceneInteractor>, private val bouncerHapticPlayer: BouncerHapticPlayer, ) { /** The bouncer action button. If `null`, the button should not be shown. */ val actionButton: Flow<BouncerActionButtonModel?> = Loading @@ -90,43 +88,38 @@ constructor( ) .map { when { isReturnToCallButton() -> returnToCallButtonModel isEmergencyCallButton() -> emergencyCallButtonModel isReturnToCallButton() -> BouncerActionButtonModel.ReturnToCallButtonModel( labelResourceId = R.string.lockscreen_return_to_call ) isEmergencyCallButton() -> BouncerActionButtonModel.EmergencyButtonModel( labelResourceId = R.string.lockscreen_emergency_call ) else -> null // Do not show the button. } } .distinctUntilChanged() } private val returnToCallButtonModel: BouncerActionButtonModel by lazy { BouncerActionButtonModel( label = applicationContext.getString(R.string.lockscreen_return_to_call), onClick = { fun onReturnToCallButtonClicked() { prepareToPerformAction() returnToCall() }, onLongClick = null, ) } private val emergencyCallButtonModel: BouncerActionButtonModel by lazy { BouncerActionButtonModel( label = applicationContext.getString(R.string.lockscreen_emergency_call), onClick = { // TODO(b/373930432): haptics should be played at the UI layer -> refactor bouncerHapticPlayer.playEmergencyButtonClickFeedback() fun onEmergencyButtonClicked() { prepareToPerformAction() dozeLogger.logEmergencyCall() startEmergencyDialerActivity() }, // TODO(b/369767936): The long click detector doesn't work properly, investigate. onLongClick = { } fun onEmergencyButtonLongClicked() { if (emergencyAffordanceManager.needsEmergencyAffordance()) { prepareToPerformAction() // TODO(b/369767936): Check that !longPressWasDragged before invoking. emergencyAffordanceManager.performEmergencyCall() } }, ) } private fun startEmergencyDialerActivity() { Loading packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt +11 −12 Original line number Diff line number Diff line Loading @@ -16,17 +16,16 @@ package com.android.systemui.bouncer.shared.model /** Models the action button on the bouncer. */ data class BouncerActionButtonModel( /** The text to be shown on the button. */ val label: String, import androidx.annotation.StringRes /** The action to perform when the user clicks on the button. */ val onClick: () -> Unit, /** Models the action button on the bouncer. */ sealed class BouncerActionButtonModel( /** The resource Id of the text to be shown on the button. */ @StringRes val labelResId: Int ) { data class EmergencyButtonModel(@StringRes private val labelResourceId: Int) : BouncerActionButtonModel(labelResourceId) /** * The action to perform when the user long-clicks on the button. When not provided, long-clicks * will be treated as regular clicks. */ val onLongClick: (() -> Unit)? = null, ) data class ReturnToCallButtonModel(@StringRes private val labelResourceId: Int) : BouncerActionButtonModel(labelResourceId) } Loading
packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +7 −4 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ 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.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics Loading Loading @@ -639,7 +640,7 @@ private fun ActionArea(viewModel: BouncerSceneContentViewModel, modifier: Modifi val appearMoveAnimatable = remember { Animatable(0f) } val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() } actionButton?.let { actionButtonViewModel -> actionButton?.let { actionButtonModel -> LaunchedEffect(Unit) { appearFadeInAnimatable.animateTo( targetValue = 1f, Loading Loading @@ -678,12 +679,14 @@ private fun ActionArea(viewModel: BouncerSceneContentViewModel, modifier: Modifi .background(color = MaterialTheme.colorScheme.tertiaryContainer) .semantics { role = Role.Button } .combinedClickable( onClick = { actionButtonViewModel.onClick() }, onLongClick = actionButtonViewModel.onLongClick?.let { { it.invoke() } }, onClick = { actionButton?.let { viewModel.onActionButtonClicked(it) } }, onLongClick = { actionButton?.let { viewModel.onActionButtonLongClicked(it) } }, ) ) { Text( text = actionButtonViewModel.label, text = stringResource(id = actionButtonModel.labelResId), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onTertiaryContainer, modifier = Modifier.align(Alignment.Center).padding(ButtonDefaults.ContentPadding), Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +15 −14 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer Loading Loading @@ -83,7 +84,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL) overrideResource( R.bool.config_enable_emergency_call_while_sim_locked, ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED, ) whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId) whenever(emergencyAffordanceManager.needsEmergencyAffordance()) Loading Loading @@ -123,11 +124,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { kosmos.fakeTelephonyRepository.setIsInCall(true) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL) assertThat(actionButton?.onClick).isNotNull() assertThat(actionButton?.onLongClick).isNull() assertThat(actionButton?.labelResId).isEqualTo(R.string.lockscreen_return_to_call) assertThat(actionButton) .isInstanceOf(BouncerActionButtonModel.ReturnToCallButtonModel::class.java) actionButton?.onClick?.invoke() underTest.onReturnToCallButtonClicked() runCurrent() assertThat(metricsLogger.logs.size).isEqualTo(1) Loading @@ -150,11 +151,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { kosmos.fakeTelephonyRepository.setIsInCall(false) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL) assertThat(actionButton?.onClick).isNotNull() assertThat(actionButton?.onLongClick).isNotNull() assertThat(actionButton?.labelResId).isEqualTo(R.string.lockscreen_emergency_call) assertThat(actionButton) .isInstanceOf(BouncerActionButtonModel.EmergencyButtonModel::class.java) actionButton?.onClick?.invoke() underTest.onEmergencyButtonClicked() runCurrent() assertThat(metricsLogger.logs.size).isEqualTo(1) Loading @@ -167,12 +168,12 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { // ActivityStarter interface here. verify(emergencyAffordanceManager, never()).performEmergencyCall() actionButton?.onLongClick?.invoke() underTest.onEmergencyButtonLongClicked() verify(emergencyAffordanceManager).performEmergencyCall() } @Test fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() = fun noCall_insecureAuthMethodButSecureSim_emergencyCallButtonIsActionButton() = testScope.runTest { val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) Loading @@ -184,9 +185,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { runCurrent() assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL) assertThat(actionButton?.onClick).isNotNull() assertThat(actionButton?.onLongClick).isNotNull() assertThat(actionButton?.labelResId).isEqualTo(R.string.lockscreen_emergency_call) assertThat(actionButton) .isInstanceOf(BouncerActionButtonModel.EmergencyButtonModel::class.java) } @Test Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -332,7 +332,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertWithMessage("Bouncer action button not visible") .that(bouncerActionButton) .isNotNull() bouncerActionButton?.onClick?.invoke() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) runCurrent() // TODO(b/369765704): Assert that an activity was started once we use ActivityStarter. Loading @@ -354,7 +354,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertWithMessage("Bouncer action button not visible during call") .that(bouncerActionButton) .isNotNull() bouncerActionButton?.onClick?.invoke() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) runCurrent() verify(kosmos.mockTelecomManager).showInCallScreen(any()) Loading
packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt +24 −31 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background Loading Loading @@ -75,7 +74,6 @@ constructor( private val metricsLogger: MetricsLogger, private val dozeLogger: DozeLogger, private val sceneInteractor: Lazy<SceneInteractor>, private val bouncerHapticPlayer: BouncerHapticPlayer, ) { /** The bouncer action button. If `null`, the button should not be shown. */ val actionButton: Flow<BouncerActionButtonModel?> = Loading @@ -90,43 +88,38 @@ constructor( ) .map { when { isReturnToCallButton() -> returnToCallButtonModel isEmergencyCallButton() -> emergencyCallButtonModel isReturnToCallButton() -> BouncerActionButtonModel.ReturnToCallButtonModel( labelResourceId = R.string.lockscreen_return_to_call ) isEmergencyCallButton() -> BouncerActionButtonModel.EmergencyButtonModel( labelResourceId = R.string.lockscreen_emergency_call ) else -> null // Do not show the button. } } .distinctUntilChanged() } private val returnToCallButtonModel: BouncerActionButtonModel by lazy { BouncerActionButtonModel( label = applicationContext.getString(R.string.lockscreen_return_to_call), onClick = { fun onReturnToCallButtonClicked() { prepareToPerformAction() returnToCall() }, onLongClick = null, ) } private val emergencyCallButtonModel: BouncerActionButtonModel by lazy { BouncerActionButtonModel( label = applicationContext.getString(R.string.lockscreen_emergency_call), onClick = { // TODO(b/373930432): haptics should be played at the UI layer -> refactor bouncerHapticPlayer.playEmergencyButtonClickFeedback() fun onEmergencyButtonClicked() { prepareToPerformAction() dozeLogger.logEmergencyCall() startEmergencyDialerActivity() }, // TODO(b/369767936): The long click detector doesn't work properly, investigate. onLongClick = { } fun onEmergencyButtonLongClicked() { if (emergencyAffordanceManager.needsEmergencyAffordance()) { prepareToPerformAction() // TODO(b/369767936): Check that !longPressWasDragged before invoking. emergencyAffordanceManager.performEmergencyCall() } }, ) } private fun startEmergencyDialerActivity() { Loading
packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt +11 −12 Original line number Diff line number Diff line Loading @@ -16,17 +16,16 @@ package com.android.systemui.bouncer.shared.model /** Models the action button on the bouncer. */ data class BouncerActionButtonModel( /** The text to be shown on the button. */ val label: String, import androidx.annotation.StringRes /** The action to perform when the user clicks on the button. */ val onClick: () -> Unit, /** Models the action button on the bouncer. */ sealed class BouncerActionButtonModel( /** The resource Id of the text to be shown on the button. */ @StringRes val labelResId: Int ) { data class EmergencyButtonModel(@StringRes private val labelResourceId: Int) : BouncerActionButtonModel(labelResourceId) /** * The action to perform when the user long-clicks on the button. When not provided, long-clicks * will be treated as regular clicks. */ val onLongClick: (() -> Unit)? = null, ) data class ReturnToCallButtonModel(@StringRes private val labelResourceId: Int) : BouncerActionButtonModel(labelResourceId) }