Merge "Add predictive back animation to Bouncer." into tm-qpr-dev

This commit is contained in:
Shan Huang
2023-01-17 20:47:45 +00:00
committed by Android (Google) Code Review
10 changed files with 213 additions and 15 deletions

View File

@@ -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<KeyguardHostView>
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),

View File

@@ -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.
*/

View File

@@ -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<Keyguard
/** Called when the bouncer changes visibility. */
public void onBouncerVisibilityChanged(@View.Visibility int visibility) {
setBouncerVisible(visibility == View.VISIBLE);
if (visibility == View.INVISIBLE) {
mView.resetScale();
}
}
private void setBouncerVisible(boolean visible) {
@@ -587,6 +592,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
return getCurrentSecurityController().needsInput();
}
/**
* @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
*/
@NonNull
OnBackAnimationCallback getBackCallback() {
return mView.getBackCallback();
}
/**
* Switches to the given security view unless it's already being shown, in which case
* this is a no-op.

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data
import android.view.KeyEvent
import android.window.OnBackAnimationCallback
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import java.lang.ref.WeakReference
@@ -51,4 +52,6 @@ interface BouncerViewDelegate {
cancelAction: Runnable?,
)
fun willDismissWithActions(): Boolean
/** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
fun getBackCallback(): OnBackAnimationCallback
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.binder
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.window.OnBackAnimationCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.internal.policy.SystemBarUtils
@@ -55,6 +56,10 @@ object KeyguardBouncerViewBinder {
mode == KeyguardSecurityModel.SecurityMode.SimPuk
}
override fun getBackCallback(): OnBackAnimationCallback {
return hostViewController.backCallback
}
override fun shouldDismissOnMenuPressed(): Boolean {
return hostViewController.shouldEnableMenuKey()
}

View File

@@ -273,7 +273,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
@Override
public void triggerBack() {
// Notify FalsingManager that an intentional gesture has occurred.
// TODO(b/186519446): use a different method than isFalseTouch
mFalsingManager.isFalseTouch(BACK_GESTURE);
// Only inject back keycodes when ahead-of-time back dispatching is disabled.
if (mBackAnimation == null) {
@@ -919,6 +918,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mThresholdCrossed = true;
// Capture inputs
mInputMonitor.pilferPointers();
if (mBackAnimation != null) {
// Notify FalsingManager that an intentional gesture has occurred.
mFalsingManager.isFalseTouch(BACK_GESTURE);
}
mInputEventReceiver.setBatchingEnabled(true);
} else {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);

View File

@@ -37,7 +37,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import android.window.OnBackInvokedCallback;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
@@ -198,11 +199,38 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
};
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
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 */);

View File

@@ -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();

View File

@@ -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;

View File

@@ -39,6 +39,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;
@@ -56,6 +58,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;
@@ -120,16 +123,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<OnBackInvokedCallback> mOnBackInvokedCallback;
private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
@Before
@@ -140,6 +146,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);
@@ -193,7 +203,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));
@@ -541,12 +552,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
@@ -555,17 +566,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);