Merge "BackEvent: add generated events for fling gesture" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
89f8d9ca79
@@ -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