BackEvent: add generated events for fling gesture
The system generates additional progress events based on finger speed. The idea is to capture the energy of the fling gesture and continue the movement adding up to 100ms. Test: atest BackAnimationControllerTest Bug: 263402927 Change-Id: I88898435680b22ffbb06e41692bb041b5da03076
This commit is contained in:
@@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
|
||||
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityTaskManager;
|
||||
@@ -37,7 +40,9 @@ import android.os.SystemClock;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings.Global;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.util.SparseArray;
|
||||
import android.view.IRemoteAnimationRunner;
|
||||
import android.view.InputDevice;
|
||||
@@ -56,6 +61,7 @@ import android.window.IOnBackInvokedCallback;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.internal.view.AppearanceRegion;
|
||||
import com.android.wm.shell.animation.FlingAnimationUtils;
|
||||
import com.android.wm.shell.common.ExternalInterfaceBinder;
|
||||
import com.android.wm.shell.common.RemoteCallable;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
@@ -80,6 +86,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
public static boolean IS_U_ANIMATION_ENABLED =
|
||||
SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
|
||||
SETTING_VALUE_ON) == SETTING_VALUE_ON;
|
||||
|
||||
public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms
|
||||
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
|
||||
|
||||
/**
|
||||
* The maximum additional progress in case of fling gesture.
|
||||
* The end animation starts after the user lifts the finger from the screen, we continue to
|
||||
* fire {@link BackEvent}s until the velocity reaches 0.
|
||||
*/
|
||||
private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */
|
||||
|
||||
/** Predictive back animation developer option */
|
||||
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
|
||||
/**
|
||||
@@ -96,6 +113,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
private boolean mShouldStartOnNextMoveEvent = false;
|
||||
/** @see #setTriggerBack(boolean) */
|
||||
private boolean mTriggerBack;
|
||||
private FlingAnimationUtils mFlingAnimationUtils;
|
||||
|
||||
@Nullable
|
||||
private BackNavigationInfo mBackNavigationInfo;
|
||||
@@ -174,6 +192,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
mBgHandler = bgHandler;
|
||||
shellInit.addInitCallback(this::onInit, this);
|
||||
mAnimationBackground = backAnimationBackground;
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics)
|
||||
.setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
|
||||
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
|
||||
.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -465,6 +488,78 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allows us to manage the fling gesture, it smoothly animates the current progress value to
|
||||
* the final position, calculated based on the current velocity.
|
||||
*
|
||||
* @param callback the callback to be invoked when the animation ends.
|
||||
*/
|
||||
private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean animationStarted = false;
|
||||
|
||||
if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) {
|
||||
|
||||
final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent();
|
||||
if (backMotionEvent != null) {
|
||||
// Constraints - absolute values
|
||||
float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
|
||||
float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
|
||||
float maxX = mTouchTracker.getMaxX(); // px
|
||||
float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px
|
||||
|
||||
// Current state
|
||||
float currentX = backMotionEvent.getTouchX();
|
||||
float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(),
|
||||
-maxVelocity, maxVelocity);
|
||||
|
||||
// Target state
|
||||
float animationFaction = velocity / maxVelocity; // value between -1 and 1
|
||||
float flingDistance = animationFaction * maxFlingDistance; // px
|
||||
float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX);
|
||||
|
||||
if (!Float.isNaN(endX)
|
||||
&& currentX != endX
|
||||
&& Math.abs(velocity) >= minVelocity) {
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX);
|
||||
|
||||
mFlingAnimationUtils.apply(
|
||||
/* animator = */ animator,
|
||||
/* currValue = */ currentX,
|
||||
/* endValue = */ endX,
|
||||
/* velocity = */ velocity,
|
||||
/* maxDistance = */ maxFlingDistance
|
||||
);
|
||||
|
||||
animator.addUpdateListener(animation -> {
|
||||
Float animatedValue = (Float) animation.getAnimatedValue();
|
||||
float progress = mTouchTracker.getProgress(animatedValue);
|
||||
final BackMotionEvent backEvent = mTouchTracker
|
||||
.createProgressEvent(progress);
|
||||
dispatchOnBackProgressed(mActiveCallback, backEvent);
|
||||
});
|
||||
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
dispatchOnBackInvoked(callback);
|
||||
}
|
||||
});
|
||||
animator.start();
|
||||
animationStarted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!animationStarted) {
|
||||
dispatchOnBackInvoked(callback);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
@@ -530,7 +625,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
if (mBackNavigationInfo != null) {
|
||||
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
|
||||
if (mTriggerBack) {
|
||||
dispatchOnBackInvoked(callback);
|
||||
dispatchOrAnimateOnBackInvoked(callback);
|
||||
} else {
|
||||
dispatchOnBackCancelled(callback);
|
||||
}
|
||||
@@ -605,7 +700,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
|
||||
// The next callback should be {@link #onBackAnimationFinished}.
|
||||
if (mTriggerBack) {
|
||||
dispatchOnBackInvoked(mActiveCallback);
|
||||
dispatchOrAnimateOnBackInvoked(mActiveCallback);
|
||||
} else {
|
||||
dispatchOnBackCancelled(mActiveCallback);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package com.android.wm.shell.back;
|
||||
|
||||
import android.annotation.FloatRange;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.MathUtils;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.window.BackEvent;
|
||||
import android.window.BackMotionEvent;
|
||||
@@ -99,28 +102,42 @@ class TouchTracker {
|
||||
}
|
||||
|
||||
BackMotionEvent createProgressEvent() {
|
||||
float progressThreshold = PROGRESS_THRESHOLD >= 0
|
||||
? PROGRESS_THRESHOLD : mProgressThreshold;
|
||||
progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
|
||||
float progress = 0;
|
||||
// Progress is always 0 when back is cancelled and not restarted.
|
||||
if (!mCancelled) {
|
||||
// If back is committed, progress is the distance between the last and first touch
|
||||
// point, divided by the max drag distance. Otherwise, it's the distance between
|
||||
// the last touch point and the starting threshold, divided by max drag distance.
|
||||
// The starting threshold is initially the first touch location, and updated to
|
||||
// the location everytime back is restarted after being cancelled.
|
||||
float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
|
||||
float deltaX = Math.max(
|
||||
mSwipeEdge == BackEvent.EDGE_LEFT
|
||||
? mLatestTouchX - startX
|
||||
: startX - mLatestTouchX,
|
||||
0);
|
||||
progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
|
||||
progress = getProgress(mLatestTouchX);
|
||||
}
|
||||
return createProgressEvent(progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress value computed from the touch position.
|
||||
*
|
||||
* @param touchX the X touch position of the {@link MotionEvent}.
|
||||
* @return progress value
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
float getProgress(float touchX) {
|
||||
// If back is committed, progress is the distance between the last and first touch
|
||||
// point, divided by the max drag distance. Otherwise, it's the distance between
|
||||
// the last touch point and the starting threshold, divided by max drag distance.
|
||||
// The starting threshold is initially the first touch location, and updated to
|
||||
// the location everytime back is restarted after being cancelled.
|
||||
float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
|
||||
float deltaX = Math.abs(startX - touchX);
|
||||
float maxX = getMaxX();
|
||||
maxX = maxX == 0 ? 1 : maxX;
|
||||
return MathUtils.constrain(deltaX / maxX, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum X value (in pixels).
|
||||
* Progress is considered to be completed (1f) when this limit is exceeded.
|
||||
*/
|
||||
float getMaxX() {
|
||||
return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
|
||||
}
|
||||
|
||||
BackMotionEvent createProgressEvent(float progress) {
|
||||
return new BackMotionEvent(
|
||||
/* touchX = */ mLatestTouchX,
|
||||
|
||||
@@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
mShellExecutor.flushAll();
|
||||
}
|
||||
|
||||
private void createNavigationInfo(int backType, boolean enableAnimation) {
|
||||
private void createNavigationInfo(int backType,
|
||||
boolean enableAnimation,
|
||||
boolean isAnimationCallback) {
|
||||
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
|
||||
.setType(backType)
|
||||
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
|
||||
.setOnBackInvokedCallback(mAppCallback)
|
||||
.setPrepareRemoteAnimation(enableAnimation);
|
||||
.setPrepareRemoteAnimation(enableAnimation)
|
||||
.setAnimationCallback(isAnimationCallback);
|
||||
|
||||
createNavigationInfo(builder);
|
||||
}
|
||||
@@ -218,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
@Test
|
||||
public void backToHome_dispatchesEvents() throws RemoteException {
|
||||
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
||||
/* enableAnimation = */ true,
|
||||
/* isAnimationCallback = */ false);
|
||||
|
||||
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
|
||||
|
||||
@@ -239,6 +244,32 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
verify(mAnimatorCallback).onBackInvoked();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException {
|
||||
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
||||
/* enableAnimation = */ true,
|
||||
/* isAnimationCallback = */ true);
|
||||
|
||||
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
|
||||
|
||||
// Check that back start and progress is dispatched when first move.
|
||||
doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000);
|
||||
|
||||
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
|
||||
verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
|
||||
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
|
||||
ArgumentCaptor<BackMotionEvent> backEventCaptor =
|
||||
ArgumentCaptor.forClass(BackMotionEvent.class);
|
||||
verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
|
||||
|
||||
// Check that back invocation is dispatched.
|
||||
mController.setTriggerBack(true); // Fake trigger back
|
||||
doMotionEvent(MotionEvent.ACTION_UP, 0);
|
||||
verify(mAnimatorCallback).onBackInvoked();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animationDisabledFromSettings() throws RemoteException {
|
||||
// Toggle the setting off
|
||||
@@ -254,7 +285,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
ArgumentCaptor<BackMotionEvent> backEventCaptor =
|
||||
ArgumentCaptor.forClass(BackMotionEvent.class);
|
||||
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
||||
/* enableAnimation = */ false,
|
||||
/* isAnimationCallback = */ false);
|
||||
|
||||
triggerBackGesture();
|
||||
releaseBackGesture();
|
||||
@@ -271,7 +304,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
@Test
|
||||
public void ignoresGesture_transitionInProgress() throws RemoteException {
|
||||
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
||||
/* enableAnimation = */ true,
|
||||
/* isAnimationCallback = */ false);
|
||||
|
||||
triggerBackGesture();
|
||||
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
@@ -309,7 +344,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
@Test
|
||||
public void acceptsGesture_transitionTimeout() throws RemoteException {
|
||||
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
||||
/* enableAnimation = */ true,
|
||||
/* isAnimationCallback = */ false);
|
||||
|
||||
// In case it is still running in animation.
|
||||
doNothing().when(mAnimatorCallback).onBackInvoked();
|
||||
@@ -334,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
public void cancelBackInvokeWhenLostFocus() throws RemoteException {
|
||||
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
|
||||
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
|
||||
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
||||
/* enableAnimation = */ true,
|
||||
/* isAnimationCallback = */ false);
|
||||
|
||||
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
|
||||
// Check that back start and progress is dispatched when first move.
|
||||
@@ -454,7 +493,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
|
||||
mController.registerAnimation(type, animationRunner);
|
||||
|
||||
createNavigationInfo(type, true);
|
||||
createNavigationInfo(type,
|
||||
/* enableAnimation = */ true,
|
||||
/* isAnimationCallback = */ false);
|
||||
|
||||
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
|
||||
|
||||
@@ -473,11 +514,15 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
}
|
||||
|
||||
private void doMotionEvent(int actionDown, int coordinate) {
|
||||
doMotionEvent(actionDown, coordinate, 0);
|
||||
}
|
||||
|
||||
private void doMotionEvent(int actionDown, int coordinate, float velocity) {
|
||||
mController.onMotionEvent(
|
||||
/* touchX */ coordinate,
|
||||
/* touchY */ coordinate,
|
||||
/* velocityX = */ 0,
|
||||
/* velocityY = */ 0,
|
||||
/* velocityX = */ velocity,
|
||||
/* velocityY = */ velocity,
|
||||
/* keyAction */ actionDown,
|
||||
/* swipeEdge */ BackEvent.EDGE_LEFT);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user