Add predictive back animation to the compose bouncer
This is not required for flexiglass as the back animation is wired up through STL transitions for flexiglass.
Fixes: 375206941
Test: verified manually,
1. Enable gesture nav
2. Open bouncer
3. Use gesture nav to go back to the lockscreen
4. bouncer should shrink in size
Flag: com.android.systemui.compose_bouncer
Change-Id: I762186ec1503ef7a3975a7745f1f9fdc78a29811
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 2187053..cd2ffc8 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
@@ -66,6 +66,7 @@
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
@@ -138,6 +139,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.
@@ -145,7 +147,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)
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/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index 76ffeb6..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
@@ -38,6 +38,8 @@
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 isUserSwitcherEnabledInConfig: Boolean
get() =
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 01cafe5..1cae916 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
@@ -37,6 +37,7 @@
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
@@ -45,6 +46,7 @@
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
@@ -139,6 +141,9 @@
/** 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
@@ -190,6 +195,22 @@
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.
*
@@ -253,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/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index caa3e3f..d2e5642 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
@@ -140,11 +140,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 {
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;