From 8ec03e8121c2830555bbd787b5809456f98b20b9 Mon Sep 17 00:00:00 2001 From: Shan Huang Date: Thu, 29 Dec 2022 23:58:58 +0000 Subject: [PATCH] Add predictive back animation to Bouncer. Bug: 238475429 Test: atest KeyguardSecurityContainerControllerTest Test: atest KeyguardSecurityContainerTest Test: atest StatusBarKeyguardViewManagerTest Test: Enable flags and swipe back on Bouncer. Change up security mode and make sure animation looks nice in all modes. Merged-In: Iaf0c265076bc4a917d9826beee74b9c3a635ef33 Change-Id: Iaf0c265076bc4a917d9826beee74b9c3a635ef33 --- .../keyguard/KeyguardHostViewController.java | 11 +++ .../keyguard/KeyguardSecurityContainer.java | 43 ++++++++++- .../KeyguardSecurityContainerController.java | 13 ++++ .../systemui/keyguard/data/BouncerView.kt | 3 + .../ui/binder/KeyguardBouncerViewBinder.kt | 5 ++ .../gestural/EdgeBackGestureHandler.java | 5 +- .../phone/StatusBarKeyguardViewManager.java | 46 ++++++++++-- ...yguardSecurityContainerControllerTest.java | 7 ++ .../KeyguardSecurityContainerTest.java | 23 ++++++ .../StatusBarKeyguardViewManagerTest.java | 72 ++++++++++++++++--- 10 files changed, 213 insertions(+), 15 deletions(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index d4ca8e34fb32c..ea84438bf4baa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -29,6 +29,9 @@ import android.view.View; import android.view.View.OnKeyListener; import android.view.ViewTreeObserver; import android.widget.FrameLayout; +import android.window.OnBackAnimationCallback; + +import androidx.annotation.NonNull; import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; @@ -393,6 +396,14 @@ public class KeyguardHostViewController extends ViewController return false; } + /** + * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture. + */ + @NonNull + public OnBackAnimationCallback getBackCallback() { + return mKeyguardSecurityContainerController.getBackCallback(); + } + /** * Allows the media keys to work when the keyguard is showing. * The media keys should be of no interest to the actual keyguard view(s), diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 5d7a6f122e694..e4f85db3971e2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -32,6 +32,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.START; import static androidx.constraintlayout.widget.ConstraintSet.TOP; import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; +import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import static java.lang.Integer.max; @@ -73,6 +74,8 @@ import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -135,7 +138,9 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private static final float MIN_DRAG_SIZE = 10; // How much to scale the default slop by, to avoid accidental drags. private static final float SLOP_SCALE = 4f; - + @VisibleForTesting + // How much the view scales down to during back gestures. + static final float MIN_BACK_SCALE = 0.9f; @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; private GlobalSettings mGlobalSettings; @@ -240,6 +245,33 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } }; + private final OnBackAnimationCallback mBackCallback = new OnBackAnimationCallback() { + @Override + public void onBackCancelled() { + // TODO(b/259608500): Remove once back API auto animates progress to 0 on cancel. + resetScale(); + } + + @Override + public void onBackInvoked() { } + + @Override + public void onBackProgressed(BackEvent event) { + float progress = event.getProgress(); + // TODO(b/263819310): Update the interpolator to match spec. + float scale = MIN_BACK_SCALE + + (1 - MIN_BACK_SCALE) * (1 - DECELERATE_QUINT.getInterpolation(progress)); + setScale(scale); + } + }; + /** + * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture. + */ + @NonNull + OnBackAnimationCallback getBackCallback() { + return mBackCallback; + } + // Used to notify the container when something interesting happens. public interface SecurityCallback { /** @@ -736,6 +768,15 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mViewMode.onDensityOrFontScaleChanged(); } + void resetScale() { + setScale(1); + } + + private void setScale(float scale) { + setScaleX(scale); + setScaleY(scale); + } + /** * Enscapsulates the differences between bouncer modes for the container. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index a72a484fb6f11..57bfe5421049a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -40,7 +40,9 @@ import android.util.Log; import android.util.Slog; import android.view.MotionEvent; import android.view.View; +import android.window.OnBackAnimationCallback; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -479,6 +481,9 @@ public class KeyguardSecurityContainerController extends ViewController { - if (DEBUG) { - Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()"); + private final OnBackAnimationCallback mOnBackInvokedCallback = new OnBackAnimationCallback() { + @Override + public void onBackInvoked() { + if (DEBUG) { + Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()"); + } + onBackPressed(); + if (shouldPlayBackAnimation()) { + mPrimaryBouncerView.getDelegate().getBackCallback().onBackInvoked(); + } + } + + @Override + public void onBackProgressed(BackEvent event) { + if (shouldPlayBackAnimation()) { + mPrimaryBouncerView.getDelegate().getBackCallback().onBackProgressed(event); + } + } + + @Override + public void onBackCancelled() { + if (shouldPlayBackAnimation()) { + mPrimaryBouncerView.getDelegate().getBackCallback().onBackCancelled(); + } + } + + @Override + public void onBackStarted(BackEvent event) { + if (shouldPlayBackAnimation()) { + mPrimaryBouncerView.getDelegate().getBackCallback().onBackStarted(event); + } } - onBackPressed(); }; private boolean mIsBackCallbackRegistered = false; @@ -256,6 +284,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mIsModernBouncerEnabled; private boolean mIsUnoccludeTransitionFlagEnabled; private boolean mIsModernAlternateBouncerEnabled; + private boolean mIsBackAnimationEnabled; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -337,6 +366,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER); mAlternateBouncerInteractor = alternateBouncerInteractor; + mIsBackAnimationEnabled = + featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); } @Override @@ -472,6 +503,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + private boolean shouldPlayBackAnimation() { + // Suppress back animation when bouncer shouldn't be dismissed on back invocation. + return !needsFullscreenBouncer() && mIsBackAnimationEnabled; + } + @Override public void onDensityOrFontScaleChanged() { hideBouncer(true /* destroyView */); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 84f6d913b310e..075ef9df9664d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -404,6 +404,13 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { verify(mSideFpsController, never()).show(any()); } + @Test + public void onBouncerVisibilityChanged_resetsScale() { + mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE); + + verify(mView).resetScale(); + } + @Test public void onStartingToHide_sideFpsHintShown_sideFpsHintHidden() { setupGetSecurityView(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 36ed669e299ce..1bbc19931c216 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -49,6 +49,8 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; @@ -357,6 +359,27 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID); } + @Test + public void testPlayBackAnimation() { + OnBackAnimationCallback backCallback = mKeyguardSecurityContainer.getBackCallback(); + backCallback.onBackStarted(createBackEvent(0, 0)); + mKeyguardSecurityContainer.getBackCallback().onBackProgressed( + createBackEvent(0, 1)); + assertThat(mKeyguardSecurityContainer.getScaleX()).isEqualTo( + KeyguardSecurityContainer.MIN_BACK_SCALE); + assertThat(mKeyguardSecurityContainer.getScaleY()).isEqualTo( + KeyguardSecurityContainer.MIN_BACK_SCALE); + + // reset scale + mKeyguardSecurityContainer.resetScale(); + assertThat(mKeyguardSecurityContainer.getScaleX()).isEqualTo(1); + assertThat(mKeyguardSecurityContainer.getScaleY()).isEqualTo(1); + } + + private BackEvent createBackEvent(float touchX, float progress) { + return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT); + } + private Configuration configuration(@Configuration.Orientation int orientation) { Configuration config = new Configuration(); config.orientation = orientation; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 04a67006d6861..7f86327ce27ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -37,6 +37,8 @@ import android.testing.TestableLooper; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; @@ -54,6 +56,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.BouncerView; import com.android.systemui.keyguard.data.BouncerViewDelegate; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; @@ -118,16 +121,19 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private BouncerView mBouncerView; @Mock private BouncerViewDelegate mBouncerViewDelegate; + @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback; private FakeKeyguardStateController mKeyguardStateController = spy(new FakeKeyguardStateController()); - @Mock private ViewRootImpl mViewRootImpl; - @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; + @Mock + private ViewRootImpl mViewRootImpl; + @Mock + private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; @Captor - private ArgumentCaptor mOnBackInvokedCallback; + private ArgumentCaptor mBackCallbackCaptor; @Before @@ -138,6 +144,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class))) .thenReturn(mKeyguardMessageAreaController); when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate); + when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback); + when(mFeatureFlags + .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM)) + .thenReturn(true); when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true); @@ -191,7 +201,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void dismissWithAction_AfterKeyguardGoneSetToFalse() { OnDismissAction action = () -> false; - Runnable cancelAction = () -> {}; + Runnable cancelAction = () -> { + }; mStatusBarKeyguardViewManager.dismissWithAction( action, cancelAction, false /* afterKeyguardGone */); verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction)); @@ -539,12 +550,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mBouncerExpansionCallback.onVisibilityChanged(true); verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), - mOnBackInvokedCallback.capture()); + mBackCallbackCaptor.capture()); /* verify that the same callback is unregistered when the bouncer becomes invisible */ mBouncerExpansionCallback.onVisibilityChanged(false); verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( - eq(mOnBackInvokedCallback.getValue())); + eq(mBackCallbackCaptor.getValue())); } @Test @@ -553,17 +564,62 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { /* capture the predictive back callback during registration */ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), - mOnBackInvokedCallback.capture()); + mBackCallbackCaptor.capture()); when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true); /* invoke the back callback directly */ - mOnBackInvokedCallback.getValue().onBackInvoked(); + mBackCallbackCaptor.getValue().onBackInvoked(); /* verify that the bouncer will be hidden as a result of the invocation */ verify(mCentralSurfaces).setBouncerShowing(eq(false)); } + @Test + public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() { + when(mKeyguardSecurityModel.getSecurityMode(anyInt())) + .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin); + mBouncerExpansionCallback.onVisibilityChanged(true); + /* capture the predictive back callback during registration */ + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), + mBackCallbackCaptor.capture()); + assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback); + + OnBackAnimationCallback backCallback = + (OnBackAnimationCallback) mBackCallbackCaptor.getValue(); + + BackEvent event = new BackEvent(0, 0, 0, BackEvent.EDGE_LEFT); + backCallback.onBackStarted(event); + verify(mBouncerViewDelegateBackCallback, never()).onBackStarted(any()); + } + + @Test + public void testPredictiveBackCallback_forwardsBackDispatches() { + mBouncerExpansionCallback.onVisibilityChanged(true); + /* capture the predictive back callback during registration */ + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), + mBackCallbackCaptor.capture()); + assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback); + + OnBackAnimationCallback backCallback = + (OnBackAnimationCallback) mBackCallbackCaptor.getValue(); + + BackEvent event = new BackEvent(0, 0, 0, BackEvent.EDGE_LEFT); + backCallback.onBackStarted(event); + verify(mBouncerViewDelegateBackCallback).onBackStarted(eq(event)); + + backCallback.onBackProgressed(event); + verify(mBouncerViewDelegateBackCallback).onBackProgressed(eq(event)); + + backCallback.onBackInvoked(); + verify(mBouncerViewDelegateBackCallback).onBackInvoked(); + + backCallback.onBackCancelled(); + verify(mBouncerViewDelegateBackCallback).onBackCancelled(); + } + @Test public void testReportBouncerOnDreamWhenVisible() { mBouncerExpansionCallback.onVisibilityChanged(true);