Merge "BackEvent: add generated events for fling gesture" into udc-dev

This commit is contained in:
Omar Miatello
2023-04-18 19:08:38 +00:00
committed by Android (Google) Code Review
3 changed files with 184 additions and 27 deletions

View File

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

View File

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

View File

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