Merge changes I789996d1,I762186ec,I880f8cde into main
* changes:
Minor cleanup of dismiss action interactor to hide state that it doesn't have to expose.
Add predictive back animation to the compose bouncer
Add support for saving the preferred input side for the bouncer
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 3b72df7..1a8c7f8 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
@@ -30,6 +30,8 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -60,14 +62,15 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-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.draw.scale
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -123,8 +126,8 @@
dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
) {
- val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle()
- val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
+ val isOneHandedModeSupported by viewModel.isOneHandedModeSupported.collectAsStateWithLifecycle()
+ val layout = calculateLayout(isOneHandedModeSupported = isOneHandedModeSupported)
BouncerContent(layout, viewModel, dialogFactory, modifier)
}
@@ -137,6 +140,7 @@
dialogFactory: BouncerDialogFactory,
modifier: Modifier,
) {
+ val scale by viewModel.scale.collectAsStateWithLifecycle()
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.
@@ -144,7 +148,7 @@
// 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).scale(scale)
) {
when (layout) {
BouncerSceneLayout.STANDARD_BOUNCER -> StandardLayout(viewModel = viewModel)
@@ -300,28 +304,54 @@
viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
- val layoutDirection = LocalLayoutDirection.current
- val isLeftToRight = layoutDirection == LayoutDirection.Ltr
- val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
+ val isLeftToRight = LocalLayoutDirection.current == LayoutDirection.Ltr
+ val isInputPreferredOnLeftSide by
+ viewModel.isInputPreferredOnLeftSide.collectAsStateWithLifecycle()
+ // Swaps the order of user switcher and bouncer input area
+ // Default layout is assumed as user switcher followed by bouncer input area in the direction
+ // of layout.
+ val isSwapped = isLeftToRight == isInputPreferredOnLeftSide
val isHeightExpanded =
LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
var swapAnimationEnd by remember { mutableStateOf(false) }
+ fun wasEventOnNonInputHalfOfScreen(x: Float, totalWidth: Int): Boolean {
+ // Default layout is assumed as user switcher followed by bouncer input area in
+ // the direction of layout. Swapped layout means that bouncer input area is first, followed
+ // by user switcher in the direction of layout.
+ val halfWidth = totalWidth / 2
+ return if (x > halfWidth) {
+ isLeftToRight && isSwapped
+ } else {
+ isLeftToRight && !isSwapped
+ }
+ }
+
Row(
modifier =
modifier
- .pointerInput(Unit) {
+ .pointerInput(isSwapped, isInputPreferredOnLeftSide) {
detectTapGestures(
onDoubleTap = { offset ->
// Depending on where the user double tapped, switch the elements such
// that the non-swapped element is closer to the side that was double
// tapped.
- setSwapped(offset.x < size.width / 2)
+ viewModel.onDoubleTap(
+ wasEventOnNonInputHalfOfScreen(offset.x, size.width)
+ )
}
)
}
+ .pointerInput(isSwapped, isInputPreferredOnLeftSide) {
+ awaitEachGesture {
+ val downEvent: PointerInputChange = awaitFirstDown()
+ viewModel.onDown(
+ wasEventOnNonInputHalfOfScreen(downEvent.position.x, size.width)
+ )
+ }
+ }
.testTag("BesideUserSwitcherLayout")
.motionTestValues {
swapAnimationEnd exportAs BouncerMotionTestKeys.swapAnimationEnd
@@ -726,7 +756,8 @@
/** 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) {
- if (!viewModel.isUserSwitcherVisible) {
+ val isUserSwitcherVisible by viewModel.isUserSwitcherVisible.collectAsStateWithLifecycle()
+ if (!isUserSwitcherVisible) {
// Take up the same space as the user switcher normally would, but with nothing inside it.
Box(modifier = modifier)
return
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 1c3d93c..eb62d33 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,19 +26,17 @@
/**
* Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
- * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by
- * [BouncerSceneLayout.STANDARD_BOUNCER].
+ * [isOneHandedModeSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced
+ * by [BouncerSceneLayout.STANDARD_BOUNCER].
*/
@Composable
-fun calculateLayout(
- isSideBySideSupported: Boolean,
-): BouncerSceneLayout {
+fun calculateLayout(isOneHandedModeSupported: Boolean): BouncerSceneLayout {
val windowSizeClass = LocalWindowSizeClass.current
return calculateLayoutInternal(
width = windowSizeClass.widthSizeClass.toEnum(),
height = windowSizeClass.heightSizeClass.toEnum(),
- isSideBySideSupported = isSideBySideSupported,
+ isOneHandedModeSupported = isOneHandedModeSupported,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 361b078..521b346 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.bouncer.domain.interactor
+import android.content.testableContext
+import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.uiEventLoggerFake
@@ -26,16 +28,22 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.authentication.shared.model.BouncerInputSide
+import com.android.systemui.bouncer.data.repository.bouncerRepository
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,7 +66,8 @@
private val authenticationInteractor = kosmos.authenticationInteractor
private val uiEventLoggerFake = kosmos.uiEventLoggerFake
- private lateinit var underTest: BouncerInteractor
+ private val underTest: BouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val testableResources by lazy { kosmos.testableContext.orCreateTestableResources }
@Before
fun setUp() {
@@ -70,8 +79,6 @@
overrideResource(R.string.kg_wrong_pin, MESSAGE_WRONG_PIN)
overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
-
- underTest = kosmos.bouncerInteractor
}
@Test
@@ -116,7 +123,7 @@
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
+ tryAutoConfirm = true,
)
)
.isEqualTo(AuthenticationResult.SUCCEEDED)
@@ -141,7 +148,7 @@
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
+ tryAutoConfirm = true,
)
)
.isEqualTo(AuthenticationResult.SKIPPED)
@@ -209,7 +216,7 @@
val tooShortPattern =
FakeAuthenticationRepository.PATTERN.subList(
0,
- kosmos.fakeAuthenticationRepository.minPatternLength - 1
+ kosmos.fakeAuthenticationRepository.minPatternLength - 1,
)
assertThat(underTest.authenticate(tooShortPattern))
.isEqualTo(AuthenticationResult.SKIPPED)
@@ -292,6 +299,77 @@
assertThat(isFaceAuthRunning).isFalse()
}
+ @Test
+ fun verifyOneHandedModeUsesTheConfigValue() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+ val oneHandedModelSupported by collectLastValue(underTest.isOneHandedModeSupported)
+
+ assertThat(oneHandedModelSupported).isFalse()
+
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ kosmos.fakeConfigurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ assertThat(oneHandedModelSupported).isTrue()
+
+ testableResources.removeOverride(R.bool.can_use_one_handed_bouncer)
+ }
+
+ @Test
+ fun verifyPreferredInputSideUsesTheSettingValue_Left() =
+ testScope.runTest {
+ val preferredInputSide by collectLastValue(underTest.preferredBouncerInputSide)
+ kosmos.bouncerRepository.setPreferredBouncerInputSide(BouncerInputSide.LEFT)
+ runCurrent()
+
+ assertThat(preferredInputSide).isEqualTo(BouncerInputSide.LEFT)
+ }
+
+ @Test
+ fun verifyPreferredInputSideUsesTheSettingValue_Right() =
+ testScope.runTest {
+ val preferredInputSide by collectLastValue(underTest.preferredBouncerInputSide)
+ underTest.setPreferredBouncerInputSide(BouncerInputSide.RIGHT)
+ runCurrent()
+
+ assertThat(preferredInputSide).isEqualTo(BouncerInputSide.RIGHT)
+
+ underTest.setPreferredBouncerInputSide(BouncerInputSide.LEFT)
+ runCurrent()
+
+ assertThat(preferredInputSide).isEqualTo(BouncerInputSide.LEFT)
+ }
+
+ @Test
+ fun preferredInputSide_defaultsToRight_whenUserSwitcherIsEnabled() =
+ testScope.runTest {
+ testableResources.addOverride(R.bool.config_enableBouncerUserSwitcher, true)
+ kosmos.fakeFeatureFlagsClassic.set(FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.bouncerRepository.preferredBouncerInputSide.value = null
+ val preferredInputSide by collectLastValue(underTest.preferredBouncerInputSide)
+
+ assertThat(preferredInputSide).isEqualTo(BouncerInputSide.RIGHT)
+ testableResources.removeOverride(R.bool.config_enableBouncerUserSwitcher)
+ }
+
+ @Test
+ fun preferredInputSide_defaultsToLeft_whenUserSwitcherIsNotEnabledAndOneHandedModeIsEnabled() =
+ testScope.runTest {
+ testableResources.addOverride(R.bool.config_enableBouncerUserSwitcher, false)
+ kosmos.fakeFeatureFlagsClassic.set(FULL_SCREEN_USER_SWITCHER, true)
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ kosmos.fakeGlobalSettings.putInt(ONE_HANDED_KEYGUARD_SIDE, -1)
+ val preferredInputSide by collectLastValue(underTest.preferredBouncerInputSide)
+
+ assertThat(preferredInputSide).isEqualTo(BouncerInputSide.LEFT)
+ testableResources.removeOverride(R.bool.config_enableBouncerUserSwitcher)
+ testableResources.removeOverride(R.bool.can_use_one_handed_bouncer)
+ }
+
companion object {
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
index 923687b..3ede841 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -25,10 +25,10 @@
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import org.junit.Test
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameter
-import platform.test.runner.parameterized.Parameters
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@@ -41,6 +41,7 @@
height = SizeClass.EXPANDED,
naturallyHeld = Vertically,
)
+
data object Tablet :
Device(
name = "tablet",
@@ -48,6 +49,7 @@
height = SizeClass.MEDIUM,
naturallyHeld = Horizontally,
)
+
data object Folded :
Device(
name = "folded",
@@ -55,6 +57,7 @@
height = SizeClass.MEDIUM,
naturallyHeld = Vertically,
)
+
data object Unfolded :
Device(
name = "unfolded",
@@ -64,6 +67,7 @@
widthWhenUnnaturallyHeld = SizeClass.MEDIUM,
heightWhenUnnaturallyHeld = SizeClass.MEDIUM,
)
+
data object TallerFolded :
Device(
name = "taller folded",
@@ -71,6 +75,7 @@
height = SizeClass.EXPANDED,
naturallyHeld = Vertically,
)
+
data object TallerUnfolded :
Device(
name = "taller unfolded",
@@ -131,7 +136,7 @@
TestCase(
device = device,
held = device.naturallyHeld,
- isSideBySideSupported = false,
+ isOneHandedModeSupported = false,
expected = STANDARD_BOUNCER,
)
)
@@ -151,7 +156,7 @@
TestCase(
device = device,
held = device.naturallyHeld.flip(),
- isSideBySideSupported = false,
+ isOneHandedModeSupported = false,
expected = STANDARD_BOUNCER,
)
)
@@ -170,7 +175,7 @@
calculateLayoutInternal(
width = device.width(whenHeld = held),
height = device.height(whenHeld = held),
- isSideBySideSupported = isSideBySideSupported,
+ isOneHandedModeSupported = isOneHandedModeSupported,
)
)
.isEqualTo(expected)
@@ -182,7 +187,7 @@
val device: Device,
val held: Held,
val expected: BouncerSceneLayout,
- val isSideBySideSupported: Boolean = true,
+ val isOneHandedModeSupported: Boolean = true,
) {
override fun toString(): String {
return buildString {
@@ -190,8 +195,8 @@
append(" width: ${device.width(held).name.lowercase(Locale.US)}")
append(" height: ${device.height(held).name.lowercase(Locale.US)}")
append(" when held $held")
- if (!isSideBySideSupported) {
- append(" (side-by-side not supported)")
+ if (!isOneHandedModeSupported) {
+ append(" (one-handed-mode not supported)")
}
}
}
@@ -242,11 +247,13 @@
sealed class Held {
abstract fun flip(): Held
}
+
data object Vertically : Held() {
override fun flip(): Held {
return Horizontally
}
}
+
data object Horizontally : Held() {
override fun flip(): Held {
return Vertically
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
index 9bddcd2..3bf4460 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.content.testableContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -166,21 +168,35 @@
}
@Test
- fun isSideBySideSupported() =
+ fun isOneHandedModeSupported() =
testScope.runTest {
- val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
+ val isOneHandedModeSupported by collectLastValue(underTest.isOneHandedModeSupported)
kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.testableContext.orCreateTestableResources.addOverride(
+ R.bool.config_enableBouncerUserSwitcher,
+ true,
+ )
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
- assertThat(isSideBySideSupported).isTrue()
+ assertThat(isOneHandedModeSupported).isTrue()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
- assertThat(isSideBySideSupported).isTrue()
+ assertThat(isOneHandedModeSupported).isTrue()
kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.testableContext.orCreateTestableResources.addOverride(
+ R.bool.can_use_one_handed_bouncer,
+ true,
+ )
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
- assertThat(isSideBySideSupported).isTrue()
+ assertThat(isOneHandedModeSupported).isTrue()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
- assertThat(isSideBySideSupported).isFalse()
+ assertThat(isOneHandedModeSupported).isFalse()
+ kosmos.testableContext.orCreateTestableResources.removeOverride(
+ R.bool.config_enableBouncerUserSwitcher
+ )
+ kosmos.testableContext.orCreateTestableResources.removeOverride(
+ R.bool.can_use_one_handed_bouncer
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index d97909a1..e149687 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -19,25 +19,24 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.tracing.coroutines.launchTraced
import com.android.compose.animation.scene.ObservableTransitionState
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.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.data.repository.Idle
@@ -45,12 +44,12 @@
import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -76,25 +75,13 @@
MockitoAnnotations.initMocks(this)
dismissInteractor = kosmos.keyguardDismissInteractor
- underTest =
- KeyguardDismissActionInteractor(
- repository = keyguardRepository,
- transitionInteractor = kosmos.keyguardTransitionInteractor,
- dismissInteractor = dismissInteractor,
- applicationScope = testScope.backgroundScope,
- deviceUnlockedInteractor = { kosmos.deviceUnlockedInteractor },
- powerInteractor = kosmos.powerInteractor,
- alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
- shadeInteractor = { kosmos.shadeInteractor },
- keyguardInteractor = { kosmos.keyguardInteractor },
- sceneInteractor = { kosmos.sceneInteractor },
- )
+ underTest = kosmos.keyguardDismissActionInteractor
}
@Test
fun updateDismissAction_onRepoChange() =
testScope.runTest {
- val dismissAction by collectLastValue(underTest.dismissAction)
+ val dismissAction by collectLastValue(keyguardRepository.dismissAction)
val newDismissAction =
DismissAction.RunImmediately(
@@ -152,11 +139,16 @@
}
@Test
- fun executeDismissAction_dismissKeyguardRequestWithImmediateDismissAction_biometricAuthed() =
+ fun dismissActionExecuted_ImmediateDismissAction_biometricAuthed() =
testScope.runTest {
- val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+ val keyguardDoneTiming by collectLastValue(kosmos.keyguardRepository.keyguardDone)
+ var wasDismissActionInvoked = false
+ startInteractor()
- val onDismissAction = { KeyguardDone.IMMEDIATE }
+ val onDismissAction = {
+ wasDismissActionInvoked = true
+ KeyguardDone.IMMEDIATE
+ }
keyguardRepository.setDismissAction(
DismissAction.RunImmediately(
onDismissAction = onDismissAction,
@@ -166,16 +158,48 @@
)
)
kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
- assertThat(executeDismissAction).isEqualTo(onDismissAction)
+ runCurrent()
+
+ assertThat(wasDismissActionInvoked).isTrue()
+ assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.IMMEDIATE)
+ assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
}
@Test
- fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
+ fun dismissActionExecuted_LaterKeyguardDoneTimingIsStored_biometricAuthed() =
testScope.runTest {
- val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+ val keyguardDoneTiming by collectLastValue(kosmos.keyguardRepository.keyguardDone)
+ var wasDismissActionInvoked = false
+ startInteractor()
+
+ val onDismissAction = {
+ wasDismissActionInvoked = true
+ KeyguardDone.LATER
+ }
+ keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = onDismissAction,
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
+ runCurrent()
+
+ assertThat(wasDismissActionInvoked).isTrue()
+ assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.LATER)
+ assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
+ }
+
+ @Test
+ fun dismissActionExecuted_WithoutImmediateDismissAction() =
+ testScope.runTest {
+ var wasDismissActionInvoked = false
+ startInteractor()
// WHEN a keyguard action will run after the keyguard is gone
- val onDismissAction = {}
+ val onDismissAction = { wasDismissActionInvoked = true }
keyguardRepository.setDismissAction(
DismissAction.RunAfterKeyguardGone(
dismissAction = onDismissAction,
@@ -184,33 +208,39 @@
willAnimateOnLockscreen = true,
)
)
- assertThat(executeDismissAction).isNull()
+ assertThat(wasDismissActionInvoked).isFalse()
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
SuccessFingerprintAuthenticationStatus(0, true)
)
kosmos.setSceneTransition(Idle(Scenes.Gone))
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
+ runCurrent()
- assertThat(executeDismissAction).isNotNull()
+ assertThat(wasDismissActionInvoked).isTrue()
+ assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
}
@Test
fun resetDismissAction() =
testScope.runTest {
kosmos.setSceneTransition(Idle(Scenes.Bouncer))
- val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+ var wasOnCancelInvoked = false
+ startInteractor()
keyguardRepository.setDismissAction(
DismissAction.RunAfterKeyguardGone(
dismissAction = {},
- onCancelAction = {},
+ onCancelAction = { wasOnCancelInvoked = true },
message = "message",
willAnimateOnLockscreen = true,
)
)
- assertThat(resetDismissAction).isNull()
+ assertThat(wasOnCancelInvoked).isFalse()
kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
- assertThat(resetDismissAction).isEqualTo(Unit)
+ runCurrent()
+
+ assertThat(wasOnCancelInvoked).isTrue()
+ assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
}
@Test
@@ -220,21 +250,25 @@
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
- val resetDismissAction by collectLastValue(underTest.resetDismissAction)
- keyguardRepository.setDismissAction(
+ var wasOnCancelInvoked = false
+
+ val dismissAction =
DismissAction.RunAfterKeyguardGone(
dismissAction = {},
- onCancelAction = {},
+ onCancelAction = { wasOnCancelInvoked = true },
message = "message",
willAnimateOnLockscreen = true,
)
- )
- assertThat(resetDismissAction).isNull()
+ keyguardRepository.setDismissAction(dismissAction)
+ assertThat(wasOnCancelInvoked).isFalse()
kosmos.setSceneTransition(
Transition(from = Scenes.Bouncer, to = Scenes.Shade, progress = flowOf(1f))
)
- assertThat(resetDismissAction).isNull()
+ runCurrent()
+
+ assertThat(wasOnCancelInvoked).isFalse()
+ assertThat(keyguardRepository.dismissAction.value).isEqualTo(dismissAction)
}
@Test
@@ -244,29 +278,34 @@
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
- val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+ var wasOnCancelInvoked = false
+ startInteractor()
+
keyguardRepository.setDismissAction(
DismissAction.RunAfterKeyguardGone(
dismissAction = {},
- onCancelAction = {},
+ onCancelAction = { wasOnCancelInvoked = true },
message = "message",
willAnimateOnLockscreen = true,
)
)
- assertThat(resetDismissAction).isNull()
+ assertThat(wasOnCancelInvoked).isFalse()
kosmos.fakePowerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.TIMEOUT,
powerButtonLaunchGestureTriggered = false,
)
- assertThat(resetDismissAction).isEqualTo(Unit)
+ runCurrent()
+
+ assertThat(wasOnCancelInvoked).isTrue()
+ assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
}
@Test
fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
testScope.runTest {
- val dismissAction by collectLastValue(underTest.dismissAction)
+ val dismissAction by collectLastValue(keyguardRepository.dismissAction)
var previousDismissActionCancelCalled = false
keyguardRepository.setDismissAction(
DismissAction.RunImmediately(
@@ -294,27 +333,6 @@
}
@Test
- fun handleDismissAction() =
- testScope.runTest {
- val dismissAction by collectLastValue(underTest.dismissAction)
- underTest.handleDismissAction()
- assertThat(dismissAction).isEqualTo(DismissAction.None)
- }
-
- @Test
- fun setKeyguardDone() =
- testScope.runTest {
- val keyguardDoneTiming by collectLastValue(dismissInteractor.keyguardDone)
- runCurrent()
-
- underTest.setKeyguardDone(KeyguardDone.LATER)
- assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.LATER)
-
- underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
- assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.IMMEDIATE)
- }
-
- @Test
@EnableSceneContainer
fun dismissAction_executesBeforeItsReset_sceneContainerOn_swipeAuth_fromQsScene() =
testScope.runTest {
@@ -324,11 +342,11 @@
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(currentScene!!)
)
+ startInteractor()
+
kosmos.sceneInteractor.setTransitionState(transitionState)
- val executeDismissAction by collectLastValue(underTest.executeDismissAction)
- val resetDismissAction by collectLastValue(underTest.resetDismissAction)
- assertThat(executeDismissAction).isNull()
- assertThat(resetDismissAction).isNull()
+ var wasDismissActionInvoked = false
+ var wasCancelActionInvoked = false
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
@@ -338,20 +356,23 @@
transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
- assertThat(executeDismissAction).isNull()
- assertThat(resetDismissAction).isNull()
+ assertThat(wasDismissActionInvoked).isFalse()
+ assertThat(wasCancelActionInvoked).isFalse()
val dismissAction =
DismissAction.RunImmediately(
- onDismissAction = { KeyguardDone.LATER },
- onCancelAction = {},
+ onDismissAction = {
+ wasDismissActionInvoked = true
+ KeyguardDone.LATER
+ },
+ onCancelAction = { wasCancelActionInvoked = true },
message = "message",
willAnimateOnLockscreen = true,
)
underTest.setDismissAction(dismissAction)
- // Should still be null because the transition to Gone has not yet happened.
- assertThat(executeDismissAction).isNull()
- assertThat(resetDismissAction).isNull()
+ // Should still not be run because the transition to Gone has not yet happened.
+ assertThat(wasDismissActionInvoked).isFalse()
+ assertThat(wasCancelActionInvoked).isFalse()
transitionState.value =
ObservableTransitionState.Transition.ChangeScene(
@@ -366,8 +387,8 @@
isInPreviewStage = flowOf(false),
)
runCurrent()
- assertThat(executeDismissAction).isNull()
- assertThat(resetDismissAction).isNull()
+ assertThat(wasDismissActionInvoked).isFalse()
+ assertThat(wasCancelActionInvoked).isFalse()
transitionState.value =
ObservableTransitionState.Transition.ChangeScene(
@@ -384,7 +405,17 @@
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
runCurrent()
- assertThat(executeDismissAction).isNotNull()
- assertThat(resetDismissAction).isNull()
+
+ assertThat(wasDismissActionInvoked).isTrue()
+ assertThat(wasCancelActionInvoked).isFalse()
}
+
+ private fun TestScope.startInteractor() {
+ testScope.backgroundScope.launchTraced(
+ "KeyguardDismissActionInteractorTest#startInteractor"
+ ) {
+ underTest.activate()
+ }
+ runCurrent()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 21a317a..b2794d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -71,6 +71,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -82,8 +83,8 @@
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -170,6 +171,7 @@
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
@Mock private SceneInteractor mSceneInteractor;
@Mock private DismissCallbackRegistry mDismissCallbackRegistry;
+ @Mock private BouncerInteractor mBouncerInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -241,7 +243,8 @@
mock(StatusBarKeyguardViewManagerInteractor.class),
mExecutor,
() -> mDeviceEntryInteractor,
- mDismissCallbackRegistry) {
+ mDismissCallbackRegistry,
+ () -> mBouncerInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -748,7 +751,8 @@
mock(StatusBarKeyguardViewManagerInteractor.class),
mExecutor,
() -> mDeviceEntryInteractor,
- mDismissCallbackRegistry) {
+ mDismissCallbackRegistry,
+ () -> mBouncerInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/BouncerInputSide.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/BouncerInputSide.kt
new file mode 100644
index 0000000..e52898d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/BouncerInputSide.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.shared.model
+
+import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT
+import com.android.systemui.authentication.shared.model.BouncerInputSide.LEFT
+import com.android.systemui.authentication.shared.model.BouncerInputSide.RIGHT
+
+/** Denotes which side of the bouncer the input area appears, applicable to large screen devices. */
+enum class BouncerInputSide(val settingValue: Int) {
+ LEFT(ONE_HANDED_KEYGUARD_SIDE_LEFT),
+ RIGHT(ONE_HANDED_KEYGUARD_SIDE_RIGHT),
+}
+
+/** Map the setting value to [BouncerInputSide] enum. */
+fun Int.toBouncerInputSide(): BouncerInputSide? {
+ return when (this) {
+ ONE_HANDED_KEYGUARD_SIDE_LEFT -> LEFT
+ ONE_HANDED_KEYGUARD_SIDE_RIGHT -> RIGHT
+ else -> null
+ }
+}
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 94e0854..f424de9 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
@@ -16,19 +16,66 @@
package com.android.systemui.bouncer.data.repository
+import android.content.Context
+import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE
+import com.android.systemui.authentication.shared.model.BouncerInputSide
+import com.android.systemui.authentication.shared.model.toBouncerInputSide
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
/** Provides access to bouncer-related application state. */
@SysUISingleton
class BouncerRepository
@Inject
constructor(
+ @Application private val applicationContext: Context,
+ private val globalSettings: GlobalSettings,
private val flags: FeatureFlagsClassic,
) {
+ val scale: MutableStateFlow<Float> = MutableStateFlow(1.0f)
+
/** Whether the user switcher should be displayed within the bouncer UI on large screens. */
- val isUserSwitcherVisible: Boolean
- get() = flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
+ val isUserSwitcherEnabledInConfig: Boolean
+ get() =
+ applicationContext.resources.getBoolean(R.bool.config_enableBouncerUserSwitcher) &&
+ flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
+
+ /** Whether the one handed bouncer is supported for this device. */
+ val isOneHandedBouncerSupportedInConfig: Boolean
+ get() = applicationContext.resources.getBoolean(R.bool.can_use_one_handed_bouncer)
+
+ /**
+ * Preferred side of the screen where the input area on the bouncer should be. This is
+ * applicable for large screen devices (foldables and tablets).
+ */
+ val preferredBouncerInputSide: MutableStateFlow<BouncerInputSide?> =
+ MutableStateFlow(getPreferredInputSideSetting())
+
+ /** X coordinate of the last recorded touch position on the lockscreen. */
+ val lastRecordedLockscreenTouchPosition = MutableStateFlow<Float?>(null)
+
+ /** Save the preferred bouncer input side. */
+ fun setPreferredBouncerInputSide(inputSide: BouncerInputSide) {
+ globalSettings.putInt(ONE_HANDED_KEYGUARD_SIDE, inputSide.settingValue)
+ // used to only trigger another emission on the flow.
+ preferredBouncerInputSide.value = inputSide
+ }
+
+ /**
+ * Record the x coordinate of the last touch position on the lockscreen. This will be used to
+ * determine which side of the bouncer the input area should be shown.
+ */
+ fun recordLockscreenTouchPosition(x: Float) {
+ lastRecordedLockscreenTouchPosition.value = x
+ }
+
+ fun getPreferredInputSideSetting(): BouncerInputSide? {
+ return globalSettings.getInt(ONE_HANDED_KEYGUARD_SIDE, -1).toBouncerInputSide()
+ }
}
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 92fcf39..e178c09 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
@@ -25,16 +25,19 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
+import com.android.systemui.authentication.shared.model.BouncerInputSide
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.log.SessionTracker
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -43,6 +46,8 @@
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -60,6 +65,7 @@
private val uiEventLogger: UiEventLogger,
private val sessionTracker: SessionTracker,
sceneBackInteractor: SceneBackInteractor,
+ private val configurationInteractor: ConfigurationInteractor,
) {
private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput
@@ -78,8 +84,47 @@
authenticationInteractor.isPinEnhancedPrivacyEnabled
/** Whether the user switcher should be displayed within the bouncer UI on large screens. */
- val isUserSwitcherVisible: Boolean
- get() = repository.isUserSwitcherVisible
+ val isUserSwitcherVisible: Flow<Boolean> =
+ authenticationInteractor.authenticationMethod.map { authMethod ->
+ when (authMethod) {
+ Sim -> false
+ else -> repository.isUserSwitcherEnabledInConfig
+ }
+ }
+
+ /**
+ * Whether one handed bouncer mode is supported on large screen devices. This allows user to
+ * double tap on the half of the screen to bring the bouncer input to that side of the screen.
+ */
+ val isOneHandedModeSupported: Flow<Boolean> =
+ combine(
+ isUserSwitcherVisible,
+ authenticationInteractor.authenticationMethod,
+ configurationInteractor.onAnyConfigurationChange,
+ ) { userSwitcherVisible, authMethod, _ ->
+ userSwitcherVisible ||
+ (repository.isOneHandedBouncerSupportedInConfig && (authMethod !is Password))
+ }
+
+ /**
+ * Preferred side of the screen where the input area on the bouncer should be. This is
+ * applicable for large screen devices (foldables and tablets).
+ */
+ val preferredBouncerInputSide: Flow<BouncerInputSide?> =
+ combine(
+ configurationInteractor.onAnyConfigurationChange,
+ repository.preferredBouncerInputSide,
+ ) { _, _ ->
+ // always read the setting as that can change outside of this
+ // repository (tests/manual testing)
+ val preferredInputSide = repository.getPreferredInputSideSetting()
+ when {
+ preferredInputSide != null -> preferredInputSide
+ repository.isUserSwitcherEnabledInConfig -> BouncerInputSide.RIGHT
+ repository.isOneHandedBouncerSupportedInConfig -> BouncerInputSide.LEFT
+ else -> null
+ }
+ }
private val _onImeHiddenByUser = MutableSharedFlow<Unit>()
/** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */
@@ -93,6 +138,12 @@
}
.map {}
+ /** X coordinate of the last recorded touch position on the lockscreen. */
+ val lastRecordedLockscreenTouchPosition = repository.lastRecordedLockscreenTouchPosition
+
+ /** Value between 0-1 that specifies by how much the bouncer UI should be scaled down. */
+ val scale: StateFlow<Float> = repository.scale.asStateFlow()
+
/** The scene to show when bouncer is dismissed. */
val dismissDestination: Flow<SceneKey> =
sceneBackInteractor.backScene
@@ -129,6 +180,37 @@
)
}
+ /** Update the preferred input side for the bouncer. */
+ fun setPreferredBouncerInputSide(inputSide: BouncerInputSide) {
+ repository.setPreferredBouncerInputSide(inputSide)
+ }
+
+ /**
+ * Record the x coordinate of the last touch position on the lockscreen. This will be used to
+ * determine which side of the bouncer the input area should be shown.
+ */
+ fun recordKeyguardTouchPosition(x: Float) {
+ // todo (b/375245685) investigate why this is not working as expected when it is
+ // wired up with SBKVM
+ repository.recordLockscreenTouchPosition(x)
+ }
+
+ fun onBackEventProgressed(progress: Float) {
+ // this is applicable only for compose bouncer without flexiglass
+ SceneContainerFlag.assertInLegacyMode()
+ repository.scale.value = (mapBackEventProgressToScale(progress))
+ }
+
+ fun onBackEventCancelled() {
+ // this is applicable only for compose bouncer without flexiglass
+ SceneContainerFlag.assertInLegacyMode()
+ repository.scale.value = DEFAULT_SCALE
+ }
+
+ fun resetScale() {
+ repository.scale.value = DEFAULT_SCALE
+ }
+
/**
* Attempts to authenticate based on the given user input.
*
@@ -180,7 +262,7 @@
} else if (authResult == AuthenticationResult.FAILED) {
uiEventLogger.log(
BouncerUiEvent.BOUNCER_PASSWORD_FAILURE,
- sessionTracker.getSessionId(SESSION_KEYGUARD)
+ sessionTracker.getSessionId(SESSION_KEYGUARD),
)
}
}
@@ -192,4 +274,15 @@
suspend fun onImeHiddenByUser() {
_onImeHiddenByUser.emit(Unit)
}
+
+ private fun mapBackEventProgressToScale(progress: Float): Float {
+ // TODO(b/263819310): Update the interpolator to match spec.
+ return MIN_BACK_SCALE + (1 - MIN_BACK_SCALE) * (1 - progress)
+ }
+
+ companion object {
+ // How much the view scales down to during back gestures.
+ private const val MIN_BACK_SCALE: Float = 0.9f
+ private const val DEFAULT_SCALE: Float = 1.0f
+ }
}
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 7f97718..554dd69 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
@@ -45,7 +45,7 @@
fun calculateLayoutInternal(
width: SizeClass,
height: SizeClass,
- isSideBySideSupported: Boolean,
+ isOneHandedModeSupported: Boolean,
): BouncerSceneLayout {
return when (height) {
SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
@@ -61,6 +61,6 @@
SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
}
- }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported }
+ }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isOneHandedModeSupported }
?: BouncerSceneLayout.STANDARD_BOUNCER
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index cc79311..47d91374 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
+import com.android.systemui.authentication.shared.model.BouncerInputSide
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -77,8 +78,8 @@
val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
_userSwitcherDropdown.asStateFlow()
- val isUserSwitcherVisible: Boolean
- get() = bouncerInteractor.isUserSwitcherVisible
+ private val _isUserSwitcherVisible = MutableStateFlow(false)
+ val isUserSwitcherVisible: StateFlow<Boolean> = _isUserSwitcherVisible.asStateFlow()
/** View-model for the current UI, based on the current authentication method. */
private val _authMethodViewModel = MutableStateFlow<AuthMethodBouncerViewModel?>(null)
@@ -118,17 +119,19 @@
*/
val actionButton: StateFlow<BouncerActionButtonModel?> = _actionButton.asStateFlow()
- private val _isSideBySideSupported =
- MutableStateFlow(isSideBySideSupported(authMethodViewModel.value))
+ private val _isOneHandedModeSupported = MutableStateFlow(false)
/**
- * Whether the "side-by-side" layout is supported.
+ * Whether the one-handed mode 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> = _isSideBySideSupported.asStateFlow()
+ val isOneHandedModeSupported: StateFlow<Boolean> = _isOneHandedModeSupported.asStateFlow()
+
+ private val _isInputPreferredOnLeftSide = MutableStateFlow(false)
+ val isInputPreferredOnLeftSide = _isInputPreferredOnLeftSide.asStateFlow()
private val _isFoldSplitRequired =
MutableStateFlow(isFoldSplitRequired(authMethodViewModel.value))
@@ -138,11 +141,15 @@
*/
val isFoldSplitRequired: StateFlow<Boolean> = _isFoldSplitRequired.asStateFlow()
+ /** How much the bouncer UI should be scaled. */
+ val scale: StateFlow<Float> = bouncerInteractor.scale
+
private val _isInputEnabled =
MutableStateFlow(authenticationInteractor.lockoutEndTimestamp == null)
private val isInputEnabled: StateFlow<Boolean> = _isInputEnabled.asStateFlow()
override suspend fun onActivated(): Nothing {
+ bouncerInteractor.resetScale()
coroutineScope {
launch { message.activate() }
launch {
@@ -200,9 +207,41 @@
launch { actionButtonInteractor.actionButton.collect { _actionButton.value = it } }
launch {
- authMethodViewModel
- .map { authMethod -> isSideBySideSupported(authMethod) }
- .collect { _isSideBySideSupported.value = it }
+ combine(
+ bouncerInteractor.isOneHandedModeSupported,
+ bouncerInteractor.lastRecordedLockscreenTouchPosition,
+ ::Pair,
+ )
+ .collect { (isOneHandedModeSupported, lastRecordedNotificationTouchPosition) ->
+ _isOneHandedModeSupported.value = isOneHandedModeSupported
+ if (
+ isOneHandedModeSupported &&
+ lastRecordedNotificationTouchPosition != null
+ ) {
+ bouncerInteractor.setPreferredBouncerInputSide(
+ if (
+ lastRecordedNotificationTouchPosition <
+ applicationContext.resources.displayMetrics.widthPixels / 2
+ ) {
+ BouncerInputSide.LEFT
+ } else {
+ BouncerInputSide.RIGHT
+ }
+ )
+ }
+ }
+ }
+
+ launch {
+ bouncerInteractor.isUserSwitcherVisible.collect {
+ _isUserSwitcherVisible.value = it
+ }
+ }
+
+ launch {
+ bouncerInteractor.preferredBouncerInputSide.collect {
+ _isInputPreferredOnLeftSide.value = it == BouncerInputSide.LEFT
+ }
}
launch {
@@ -221,10 +260,6 @@
}
}
- private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
- return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
- }
-
private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
return authMethod !is PasswordBouncerViewModel
}
@@ -335,6 +370,29 @@
}
/**
+ * Notifies that double tap gesture was detected on the bouncer.
+ * [wasEventOnNonInputHalfOfScreen] is true when it happens on the side of the bouncer where the
+ * input UI is not present.
+ */
+ fun onDoubleTap(wasEventOnNonInputHalfOfScreen: Boolean) {
+ if (!wasEventOnNonInputHalfOfScreen) return
+ if (_isInputPreferredOnLeftSide.value) {
+ bouncerInteractor.setPreferredBouncerInputSide(BouncerInputSide.RIGHT)
+ } else {
+ bouncerInteractor.setPreferredBouncerInputSide(BouncerInputSide.LEFT)
+ }
+ }
+
+ /**
+ * Notifies that onDown was detected on the bouncer. [wasEventOnNonInputHalfOfScreen] is true
+ * when it happens on the side of the bouncer where the input UI is not present.
+ */
+ fun onDown(wasEventOnNonInputHalfOfScreen: Boolean) {
+ if (!wasEventOnNonInputHalfOfScreen) return
+ bouncerInteractor.onDown()
+ }
+
+ /**
* Notifies that a key event has occurred.
*
* @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 258232b..21090c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -27,28 +29,31 @@
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
-import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Encapsulates business-logic for actions to run when the keyguard is dismissed. */
@ExperimentalCoroutinesApi
@@ -66,10 +71,10 @@
shadeInteractor: Lazy<ShadeInteractor>,
keyguardInteractor: Lazy<KeyguardInteractor>,
sceneInteractor: Lazy<SceneInteractor>,
-) {
- val dismissAction: Flow<DismissAction> = repository.dismissAction
-
- val onCancel: Flow<Runnable> = dismissAction.map { it.onCancelAction }
+ private val keyguardLogger: KeyguardLogger,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+) : ExclusiveActivatable() {
+ private val dismissAction: Flow<DismissAction> = repository.dismissAction
// TODO (b/268240415): use message in alt + primary bouncer message
// message to show to the user about the dismiss action, else empty string
@@ -90,10 +95,24 @@
)
private val finishedTransitionToGone: Flow<Unit> =
- transitionInteractor
- .isFinishedIn(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
- .filter { it }
- .map {}
+ if (SceneContainerFlag.isEnabled) {
+ // Using sceneInteractor instead of transitionInteractor because of a race
+ // condition that forms between transitionInteractor (transitionState) and
+ // isOnShadeWhileUnlocked where the latter emits false before the former emits
+ // true, causing the merge to not emit until it's too late.
+ sceneInteractor
+ .get()
+ .currentScene
+ .map { it == Scenes.Gone }
+ .distinctUntilChanged()
+ .filter { it }
+ .map {}
+ } else {
+ transitionInteractor
+ .isFinishedIn(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .filter { it }
+ .map {}
+ }
/**
* True if the any variation of the notification shade or quick settings is showing AND the
@@ -125,30 +144,8 @@
}
}
- val executeDismissAction: Flow<() -> KeyguardDone> =
- merge(
- if (SceneContainerFlag.isEnabled) {
- // Using currentScene instead of finishedTransitionToGone because of a race
- // condition that forms between finishedTransitionToGone and
- // isOnShadeWhileUnlocked where the latter emits false before the former emits
- // true, causing the merge to not emit until it's too late.
- sceneInteractor
- .get()
- .currentScene
- .map { it == Scenes.Gone }
- .distinctUntilChanged()
- .filter { it }
- } else {
- finishedTransitionToGone
- },
- isOnShadeWhileUnlocked.filter { it }.map {},
- dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction,
- )
- .sample(dismissAction)
- .filterNot { it is DismissAction.None }
- .map { it.onDismissAction }
-
- val resetDismissAction: Flow<Unit> =
+ /** Flow that emits whenever we need to reset the dismiss action */
+ private val resetDismissAction: Flow<Unit> =
combine(
if (SceneContainerFlag.isEnabled) {
// Using currentScene instead of isFinishedIn because of a race condition that
@@ -205,13 +202,62 @@
repository.setDismissAction(dismissAction)
}
- fun handleDismissAction() {
- if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) return
+ /** Launch any relevant coroutines that are required by this interactor. */
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch {
+ merge(finishedTransitionToGone, isOnShadeWhileUnlocked.filter { it }.map {})
+ .collect {
+ log("finishedTransitionToGone")
+ runDismissAction()
+ }
+ }
+
+ launch {
+ dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction.collect {
+ log("eventsThatRequireKeyguardDismissal")
+ runDismissAction()
+ }
+ }
+
+ launch {
+ resetDismissAction.collect {
+ log("resetDismissAction")
+ repository.dismissAction.value.onCancelAction.run()
+ clearDismissAction()
+ }
+ }
+
+ launch { repository.dismissAction.collect { log("updatedDismissAction=$it") } }
+ awaitCancellation()
+ }
+ }
+
+ /** Run the dismiss action and starts the dismiss keyguard transition. */
+ private suspend fun runDismissAction() {
+ val dismissAction = repository.dismissAction.value
+ var keyguardDoneTiming: KeyguardDone = KeyguardDone.IMMEDIATE
+ if (dismissAction != DismissAction.None) {
+ keyguardDoneTiming = dismissAction.onDismissAction.invoke()
+ dismissInteractor.setKeyguardDone(keyguardDoneTiming)
+ clearDismissAction()
+ }
+ if (!SceneContainerFlag.isEnabled) {
+ // This is required to reset some state flows in the repository which ideally should be
+ // sharedFlows but are not due to performance concerns.
+ primaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+ }
+ }
+
+ private fun clearDismissAction() {
repository.setDismissAction(DismissAction.None)
}
- suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
- if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) return
- dismissInteractor.setKeyguardDone(keyguardDoneTiming)
+ private fun log(message: String) {
+ keyguardLogger.log(TAG, LogLevel.DEBUG, message)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardDismissAction"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index 87befc0..f1a316c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -15,14 +15,11 @@
*/
package com.android.systemui.keyguard.ui.binder
-import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.CoreStartable
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,7 +34,6 @@
constructor(
private val interactorLazy: Lazy<KeyguardDismissActionInteractor>,
@Application private val scope: CoroutineScope,
- private val keyguardLogger: KeyguardLogger,
) : CoreStartable {
override fun start() {
@@ -45,31 +41,6 @@
return
}
- val interactor = interactorLazy.get()
- scope.launch {
- interactor.executeDismissAction.collect {
- log("executeDismissAction")
- interactor.setKeyguardDone(it())
- interactor.handleDismissAction()
- }
- }
-
- scope.launch {
- interactor.resetDismissAction.sample(interactor.onCancel).collect {
- log("resetDismissAction")
- it.run()
- interactor.handleDismissAction()
- }
- }
-
- scope.launch { interactor.dismissAction.collect { log("updatedDismissAction=$it") } }
- }
-
- private fun log(message: String) {
- keyguardLogger.log(TAG, LogLevel.DEBUG, message)
- }
-
- companion object {
- private const val TAG = "KeyguardDismissAction"
+ scope.launch { interactorLazy.get().activate() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 92b609e..9cda199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -59,6 +59,7 @@
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -165,6 +166,7 @@
private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+ private final Lazy<BouncerInteractor> mBouncerInteractor;
private final BouncerView mPrimaryBouncerView;
private final Lazy<ShadeController> mShadeController;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
@@ -252,6 +254,9 @@
@Override
public void onBackProgressedCompat(@NonNull BackEvent event) {
+ if (ComposeBouncerFlags.INSTANCE.isOnlyComposeBouncerEnabled()) {
+ mBouncerInteractor.get().onBackEventProgressed(event.getProgress());
+ }
if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
mPrimaryBouncerView.getDelegate().getBackCallback().onBackProgressed(event);
}
@@ -259,6 +264,9 @@
@Override
public void onBackCancelledCompat() {
+ if (ComposeBouncerFlags.INSTANCE.isOnlyComposeBouncerEnabled()) {
+ mBouncerInteractor.get().onBackEventCancelled();
+ }
if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
mPrimaryBouncerView.getDelegate().getBackCallback().onBackCancelled();
}
@@ -400,7 +408,8 @@
StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor,
@Main DelayableExecutor executor,
Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy,
- DismissCallbackRegistry dismissCallbackRegistry
+ DismissCallbackRegistry dismissCallbackRegistry,
+ Lazy<BouncerInteractor> bouncerInteractor
) {
mContext = context;
mExecutor = executor;
@@ -424,6 +433,7 @@
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mBouncerInteractor = bouncerInteractor;
mIsBackAnimationEnabled = predictiveBackAnimateBouncer();
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 6e36d42b..9ace8e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bouncer.ui.composable
import android.app.AlertDialog
+import android.content.testableContext
import android.platform.test.annotations.MotionTest
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -38,8 +39,10 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.motion.createSysUiComposeMotionTestRule
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.testKosmos
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -76,6 +79,17 @@
kosmos.sceneContainerStartable.start()
kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.testableContext.orCreateTestableResources.addOverride(
+ R.bool.config_enableBouncerUserSwitcher,
+ true,
+ )
+ }
+
+ @After
+ fun teardown() {
+ kosmos.testableContext.orCreateTestableResources.removeOverride(
+ R.bool.config_enableBouncerUserSwitcher
+ )
}
@Composable
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
index c0f8638..61698f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
@@ -16,12 +16,16 @@
package com.android.systemui.bouncer.data.repository
+import android.content.applicationContext
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.settings.fakeGlobalSettings
val Kosmos.bouncerRepository by Fixture {
BouncerRepository(
+ applicationContext = applicationContext,
flags = featureFlagsClassic,
+ globalSettings = fakeGlobalSettings,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index 4394847..d27ecce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.data.repository.bouncerRepository
import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -39,5 +40,6 @@
uiEventLogger = uiEventLogger,
sessionTracker = sessionTracker,
sceneBackInteractor = sceneBackInteractor,
+ configurationInteractor = configurationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 2f13ba4..bd841ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -16,11 +16,14 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -40,5 +43,7 @@
shadeInteractor = { shadeInteractor },
keyguardInteractor = { keyguardInteractor },
sceneInteractor = { sceneInteractor },
+ keyguardLogger = KeyguardLogger(logcatLogBuffer("keyguard-logger-for-test")),
+ primaryBouncerInteractor = primaryBouncerInteractor,
)
}