DO NOT MERGE Spring controlled back progress API am: 188dee2b3b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20107349

Change-Id: Ibf79d93948ee263af7d867b4245f51187c94e552
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Shan Huang
2022-10-22 00:11:52 +00:00
committed by Automerger Merge Worker
11 changed files with 513 additions and 123 deletions

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.window;
import android.util.FloatProperty;
import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
/**
* An animator that drives the predictive back progress with a spring.
*
* The back gesture's latest touch point and committal state determines the final position of
* the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with
* smoothly transitioning progress values.
*
* @hide
*/
public class BackProgressAnimator {
/**
* A factor to scale the input progress by, so that it works better with the spring.
* We divide the output progress by this value before sending it to apps, so that apps
* always receive progress values in [0, 1].
*/
private static final float SCALE_FACTOR = 100f;
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
private BackEvent mLastBackEvent;
private boolean mStarted = false;
private void setProgress(float progress) {
mProgress = progress;
}
private float getProgress() {
return mProgress;
}
private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP =
new FloatProperty<BackProgressAnimator>("progress") {
@Override
public void setValue(BackProgressAnimator animator, float value) {
animator.setProgress(value);
animator.updateProgressValue(value);
}
@Override
public Float get(BackProgressAnimator object) {
return object.getProgress();
}
};
/** A callback to be invoked when there's a progress value update from the animator. */
public interface ProgressCallback {
/** Called when there's a progress value update. */
void onProgressUpdate(BackEvent event);
}
public BackProgressAnimator() {
mSpring = new SpringAnimation(this, PROGRESS_PROP);
mSpring.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
}
/**
* Sets a new target position for the back progress.
*
* @param event the {@link BackEvent} containing the latest target progress.
*/
public void onBackProgressed(BackEvent event) {
if (!mStarted) {
return;
}
mLastBackEvent = event;
mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR);
}
/**
* Starts the back progress animation.
*
* @param event the {@link BackEvent} that started the gesture.
* @param callback the back callback to invoke for the gesture. It will receive back progress
* dispatches as the progress animation updates.
*/
public void onBackStarted(BackEvent event, ProgressCallback callback) {
reset();
mLastBackEvent = event;
mCallback = callback;
mStarted = true;
}
/**
* Resets the back progress animation. This should be called when back is invoked or cancelled.
*/
public void reset() {
mSpring.animateToFinalPosition(0);
if (mSpring.canSkipToEnd()) {
mSpring.skipToEnd();
} else {
// Should never happen.
mSpring.cancel();
}
mStarted = false;
mLastBackEvent = null;
mCallback = null;
mProgress = 0;
}
private void updateProgressValue(float progress) {
if (mLastBackEvent == null || mCallback == null || !mStarted) {
return;
}
mCallback.onProgressUpdate(
new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
mLastBackEvent.getDepartingAnimationTarget()));
}
}

View File

@@ -28,17 +28,18 @@ import android.window.BackEvent;
oneway interface IOnBackInvokedCallback { oneway interface IOnBackInvokedCallback {
/** /**
* Called when a back gesture has been started, or back button has been pressed down. * Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted()}. * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
*
* @param backEvent The {@link BackEvent} containing information about the touch or button press.
*/ */
void onBackStarted(); void onBackStarted(in BackEvent backEvent);
/** /**
* Called on back gesture progress. * Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed()}. * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
* *
* @param touchX Absolute X location of the touch point. * @param backEvent The {@link BackEvent} containing information about the latest touch point
* @param touchY Absolute Y location of the touch point. * and the progress that the back animation should seek to.
* @param progress Value between 0 and 1 on how far along the back gesture is.
*/ */
void onBackProgressed(in BackEvent backEvent); void onBackProgressed(in BackEvent backEvent);

View File

@@ -40,10 +40,12 @@ import android.view.View;
* @hide * @hide
*/ */
public interface OnBackAnimationCallback extends OnBackInvokedCallback { public interface OnBackAnimationCallback extends OnBackInvokedCallback {
/** /**
* Called when a back gesture has been started, or back button has been pressed down. * Called when a back gesture has been started, or back button has been pressed down.
*/ *
default void onBackStarted() { } * @param backEvent An {@link BackEvent} object describing the progress event.
*/
default void onBackStarted(@NonNull BackEvent backEvent) {}
/** /**
* Called on back gesture progress. * Called on back gesture progress.

View File

@@ -16,6 +16,7 @@
package android.window; package android.window;
import android.annotation.NonNull;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.view.Window; import android.view.Window;
@@ -40,9 +41,36 @@ import android.view.Window;
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public interface OnBackInvokedCallback { public interface OnBackInvokedCallback {
/**
* Called when a back gesture has been started, or back button has been pressed down.
*
* @param backEvent The {@link BackEvent} containing information about the touch or
* button press.
*
* @hide
*/
default void onBackStarted(@NonNull BackEvent backEvent) {}
/**
* Called when a back gesture has been progressed.
*
* @param backEvent The {@link BackEvent} containing information about the latest touch point
* and the progress that the back animation should seek to.
*
* @hide
*/
default void onBackProgressed(@NonNull BackEvent backEvent) {}
/** /**
* Called when a back gesture has been completed and committed, or back button pressed * Called when a back gesture has been completed and committed, or back button pressed
* has been released and committed. * has been released and committed.
*/ */
void onBackInvoked(); void onBackInvoked();
/**
* Called when a back gesture or button press has been cancelled.
*
* @hide
*/
default void onBackCancelled() {}
} }

View File

@@ -218,19 +218,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
public Checker getChecker() { public Checker getChecker() {
return mChecker; return mChecker;
} }
@NonNull
private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback; private final WeakReference<OnBackInvokedCallback> mCallback;
OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
mCallback = new WeakReference<>(callback); mCallback = new WeakReference<>(callback);
} }
@Override @Override
public void onBackStarted() { public void onBackStarted(BackEvent backEvent) {
Handler.getMain().post(() -> { Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback(); final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) { if (callback != null) {
callback.onBackStarted(); mProgressAnimator.onBackStarted(backEvent, event ->
callback.onBackProgressed(event));
callback.onBackStarted(backEvent);
} }
}); });
} }
@@ -240,7 +245,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
Handler.getMain().post(() -> { Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback(); final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) { if (callback != null) {
callback.onBackProgressed(backEvent); mProgressAnimator.onBackProgressed(backEvent);
} }
}); });
} }
@@ -248,6 +253,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override @Override
public void onBackCancelled() { public void onBackCancelled() {
Handler.getMain().post(() -> { Handler.getMain().post(() -> {
mProgressAnimator.reset();
final OnBackAnimationCallback callback = getBackAnimationCallback(); final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) { if (callback != null) {
callback.onBackCancelled(); callback.onBackCancelled();
@@ -258,6 +264,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override @Override
public void onBackInvoked() throws RemoteException { public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> { Handler.getMain().post(() -> {
mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallback.get(); final OnBackInvokedCallback callback = mCallback.get();
if (callback == null) { if (callback == null) {
return; return;

View File

@@ -60,6 +60,8 @@ public class WindowOnBackInvokedDispatcherTest {
private OnBackAnimationCallback mCallback1; private OnBackAnimationCallback mCallback1;
@Mock @Mock
private OnBackAnimationCallback mCallback2; private OnBackAnimationCallback mCallback2;
@Mock
private BackEvent mBackEvent;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -85,14 +87,14 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo( verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
Mockito.eq(mWindow), Mockito.eq(mWindow),
captor.capture()); captor.capture());
captor.getAllValues().get(0).getCallback().onBackStarted(); captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle(); waitForIdle();
verify(mCallback1).onBackStarted(); verify(mCallback1).onBackStarted(mBackEvent);
verifyZeroInteractions(mCallback2); verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).getCallback().onBackStarted(); captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle(); waitForIdle();
verify(mCallback2).onBackStarted(); verify(mCallback2).onBackStarted(mBackEvent);
verifyNoMoreInteractions(mCallback1); verifyNoMoreInteractions(mCallback1);
} }
@@ -110,9 +112,9 @@ public class WindowOnBackInvokedDispatcherTest {
Mockito.eq(mWindow), captor.capture()); Mockito.eq(mWindow), captor.capture());
verifyNoMoreInteractions(mWindowSession); verifyNoMoreInteractions(mWindowSession);
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY); assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
captor.getValue().getCallback().onBackStarted(); captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle(); waitForIdle();
verify(mCallback1).onBackStarted(); verify(mCallback1).onBackStarted(mBackEvent);
} }
@Test @Test
@@ -148,8 +150,8 @@ public class WindowOnBackInvokedDispatcherTest {
mDispatcher.registerOnBackInvokedCallback( mDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2); OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
captor.getValue().getCallback().onBackStarted(); captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle(); waitForIdle();
verify(mCallback2).onBackStarted(); verify(mCallback2).onBackStarted(mBackEvent);
} }
} }

View File

@@ -75,13 +75,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private static final String TAG = "BackAnimationController"; private static final String TAG = "BackAnimationController";
private static final int SETTING_VALUE_OFF = 0; private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1; private static final int SETTING_VALUE_ON = 1;
private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
"persist.wm.debug.predictive_back_progress_threshold";
public static final boolean IS_ENABLED = public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back", SystemProperties.getInt("persist.wm.debug.predictive_back",
SETTING_VALUE_ON) != SETTING_VALUE_OFF; SETTING_VALUE_ON) == SETTING_VALUE_ON;
private static final int PROGRESS_THRESHOLD = SystemProperties /** Flag for U animation features */
.getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1); public static boolean IS_U_ANIMATION_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
SETTING_VALUE_OFF) == SETTING_VALUE_ON;
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
// TODO (b/241808055) Find a appropriate time to remove during refactor // TODO (b/241808055) Find a appropriate time to remove during refactor
private static final boolean USE_TRANSITION = private static final boolean USE_TRANSITION =
@@ -114,7 +115,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Nullable @Nullable
private IOnBackInvokedCallback mBackToLauncherCallback; private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold; private float mTriggerThreshold;
private float mProgressThreshold;
private final Runnable mResetTransitionRunnable = () -> { private final Runnable mResetTransitionRunnable = () -> {
finishAnimation(); finishAnimation();
mTransitionInProgress = false; mTransitionInProgress = false;
@@ -125,7 +125,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private IBackNaviAnimationController mBackAnimationController; private IBackNaviAnimationController mBackAnimationController;
private BackAnimationAdaptor mBackAnimationAdaptor; private BackAnimationAdaptor mBackAnimationAdaptor;
private boolean mWaitingAnimationStart;
private final TouchTracker mTouchTracker = new TouchTracker(); private final TouchTracker mTouchTracker = new TouchTracker();
private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher(); private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher();
@@ -147,44 +146,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
} }
}; };
/**
* Helper class to record the touch location for gesture start and latest.
*/
private static class TouchTracker {
/**
* Location of the latest touch event
*/
private float mLatestTouchX;
private float mLatestTouchY;
private int mSwipeEdge;
/**
* Location of the initial touch event of the back gesture.
*/
private float mInitTouchX;
private float mInitTouchY;
void update(float touchX, float touchY, int swipeEdge) {
mLatestTouchX = touchX;
mLatestTouchY = touchY;
mSwipeEdge = swipeEdge;
}
void setGestureStartLocation(float touchX, float touchY) {
mInitTouchX = touchX;
mInitTouchY = touchY;
}
int getDeltaFromGestureStart(float touchX) {
return Math.round(touchX - mInitTouchX);
}
void reset() {
mInitTouchX = 0;
mInitTouchY = 0;
}
}
/** /**
* Cache the temporary callback and trigger result if gesture was finish before received * Cache the temporary callback and trigger result if gesture was finish before received
* BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation. * BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
@@ -212,15 +173,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
boolean consumed = false; boolean consumed = false;
if (mWaitingAnimation && mOnBackCallback != null) { if (mWaitingAnimation && mOnBackCallback != null) {
if (mTriggerBack) { if (mTriggerBack) {
final BackEvent backFinish = new BackEvent( final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
mTouchTracker.mSwipeEdge, mAnimationTarget);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackInvoked(mOnBackCallback); dispatchOnBackInvoked(mOnBackCallback);
} else { } else {
final BackEvent backFinish = new BackEvent( final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
mTouchTracker.mSwipeEdge, mAnimationTarget);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackCancelled(mOnBackCallback); dispatchOnBackCancelled(mOnBackCallback);
} }
@@ -263,6 +220,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
shellInit.addInitCallback(this::onInit, this); shellInit.addInitCallback(this::onInit, this);
} }
@VisibleForTesting
void setEnableUAnimation(boolean enable) {
IS_U_ANIMATION_ENABLED = enable;
}
private void onInit() { private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
@@ -404,7 +366,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return; return;
} }
mTouchTracker.update(touchX, touchY, swipeEdge); mTouchTracker.update(touchX, touchY);
if (keyAction == MotionEvent.ACTION_DOWN) { if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) { if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true; mShouldStartOnNextMoveEvent = true;
@@ -414,7 +376,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// Let the animation initialized here to make sure the onPointerDownOutsideFocus // Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we // could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation. // would access it when startBackNavigation.
onGestureStarted(touchX, touchY); onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false; mShouldStartOnNextMoveEvent = false;
} }
onMove(touchX, touchY, swipeEdge); onMove(touchX, touchY, swipeEdge);
@@ -428,14 +390,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
} }
} }
private void onGestureStarted(float touchX, float touchY) { private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) { if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started."); Log.e(TAG, "Animation is being initialized but is already started.");
finishAnimation(); finishAnimation();
} }
mTouchTracker.setGestureStartLocation(touchX, touchY); mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
mBackGestureStarted = true; mBackGestureStarted = true;
try { try {
@@ -464,6 +426,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
displayTargetScreenshot(hardwareBuffer, displayTargetScreenshot(hardwareBuffer,
backNavigationInfo.getTaskWindowConfiguration()); backNavigationInfo.getTaskWindowConfiguration());
} }
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
mTransaction.apply(); mTransaction.apply();
} else if (dispatchToLauncher) { } else if (dispatchToLauncher) {
targetCallback = mBackToLauncherCallback; targetCallback = mBackToLauncherCallback;
@@ -474,7 +437,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
} }
if (!USE_TRANSITION || !dispatchToLauncher) { if (!USE_TRANSITION || !dispatchToLauncher) {
dispatchOnBackStarted(targetCallback); dispatchOnBackStarted(
targetCallback,
mTouchTracker.createStartEvent(
mBackNavigationInfo.getDepartingAnimationTarget()));
} }
} }
@@ -514,29 +480,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted || mBackNavigationInfo == null) { if (!mBackGestureStarted || mBackNavigationInfo == null) {
return; return;
} }
int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX); final BackEvent backEvent = mTouchTracker.createProgressEvent();
float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
if (USE_TRANSITION) {
if (mBackAnimationController != null && mAnimationTarget != null) {
final BackEvent backEvent = new BackEvent(
touchX, touchY, progress, swipeEdge, mAnimationTarget);
dispatchOnBackProgressed(mBackToLauncherCallback, backEvent); dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
} } else if (mEnableAnimations.get()) {
} else {
int backType = mBackNavigationInfo.getType(); int backType = mBackNavigationInfo.getType();
RemoteAnimationTarget animationTarget = IOnBackInvokedCallback targetCallback;
mBackNavigationInfo.getDepartingAnimationTarget();
BackEvent backEvent = new BackEvent(
touchX, touchY, progress, swipeEdge, animationTarget);
IOnBackInvokedCallback targetCallback = null;
if (shouldDispatchToLauncher(backType)) { if (shouldDispatchToLauncher(backType)) {
targetCallback = mBackToLauncherCallback; targetCallback = mBackToLauncherCallback;
} else if (backType == BackNavigationInfo.TYPE_CROSS_TASK } else {
|| backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
// TODO(208427216) Run the actual animation
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
} }
dispatchOnBackProgressed(targetCallback, backEvent); dispatchOnBackProgressed(targetCallback, backEvent);
@@ -620,18 +572,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|| mBackNavigationInfo.getDepartingAnimationTarget() != null); || mBackNavigationInfo.getDepartingAnimationTarget() != null);
} }
private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) { private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
BackEvent backEvent) {
if (callback == null) { if (callback == null) {
return; return;
} }
try { try {
callback.onBackStarted(); if (shouldDispatchAnimation(callback)) {
callback.onBackStarted(backEvent);
}
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e); Log.e(TAG, "dispatchOnBackStarted error: ", e);
} }
} }
private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
if (callback == null) { if (callback == null) {
return; return;
} }
@@ -642,29 +597,38 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
} }
} }
private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) { private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
if (callback == null) { if (callback == null) {
return; return;
} }
try { try {
callback.onBackCancelled(); if (shouldDispatchAnimation(callback)) {
callback.onBackCancelled();
}
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e); Log.e(TAG, "dispatchOnBackCancelled error: ", e);
} }
} }
private static void dispatchOnBackProgressed(IOnBackInvokedCallback callback, private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
BackEvent backEvent) { BackEvent backEvent) {
if (callback == null) { if (callback == null) {
return; return;
} }
try { try {
callback.onBackProgressed(backEvent); if (shouldDispatchAnimation(callback)) {
callback.onBackProgressed(backEvent);
}
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e); Log.e(TAG, "dispatchOnBackProgressed error: ", e);
} }
} }
private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
return (IS_U_ANIMATION_ENABLED || callback == mBackToLauncherCallback)
&& mEnableAnimations.get();
}
/** /**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise. * Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/ */
@@ -673,10 +637,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return; return;
} }
mTriggerBack = triggerBack; mTriggerBack = triggerBack;
mTouchTracker.setTriggerBack(triggerBack);
} }
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) { private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
mProgressThreshold = progressThreshold; mTouchTracker.setProgressThreshold(progressThreshold);
mTriggerThreshold = triggerThreshold; mTriggerThreshold = triggerThreshold;
} }
@@ -686,6 +651,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
BackNavigationInfo backNavigationInfo = mBackNavigationInfo; BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
boolean triggerBack = mTriggerBack; boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null; mBackNavigationInfo = null;
mAnimationTarget = null;
mTriggerBack = false; mTriggerBack = false;
mShouldStartOnNextMoveEvent = false; mShouldStartOnNextMoveEvent = false;
if (backNavigationInfo == null) { if (backNavigationInfo == null) {
@@ -762,17 +728,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
tx.apply(); tx.apply();
} }
} }
// TODO animation target should be passed at onBackStarted dispatchOnBackStarted(mBackToLauncherCallback,
dispatchOnBackStarted(mBackToLauncherCallback); mTouchTracker.createStartEvent(mAnimationTarget));
// TODO This is Workaround for LauncherBackAnimationController, there will need final BackEvent backInit = mTouchTracker.createProgressEvent();
// to dispatch onBackProgressed twice(startBack & updateBackProgress) to
// initialize the animation data, for now that would happen when onMove
// called, but there will no expected animation if the down -> up gesture
// happen very fast which ACTION_MOVE only happen once.
final BackEvent backInit = new BackEvent(
mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
mTouchTracker.mSwipeEdge, mAnimationTarget);
dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
if (!mCachingBackDispatcher.consume()) { if (!mCachingBackDispatcher.consume()) {
dispatchOnBackProgressed(mBackToLauncherCallback, backInit); dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
} }

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.back;
import android.os.SystemProperties;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
/**
* Helper class to record the touch location for gesture and generate back events.
*/
class TouchTracker {
private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
"persist.wm.debug.predictive_back_progress_threshold";
private static final int PROGRESS_THRESHOLD = SystemProperties
.getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
private float mProgressThreshold;
/**
* Location of the latest touch event
*/
private float mLatestTouchX;
private float mLatestTouchY;
private boolean mTriggerBack;
/**
* Location of the initial touch event of the back gesture.
*/
private float mInitTouchX;
private float mInitTouchY;
private float mStartThresholdX;
private int mSwipeEdge;
private boolean mCancelled;
void update(float touchX, float touchY) {
/**
* If back was previously cancelled but the user has started swiping in the forward
* direction again, restart back.
*/
if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT)
|| touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
mCancelled = false;
mStartThresholdX = touchX;
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
}
void setTriggerBack(boolean triggerBack) {
if (mTriggerBack != triggerBack && !triggerBack) {
mCancelled = true;
}
mTriggerBack = triggerBack;
}
void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
mInitTouchX = touchX;
mInitTouchY = touchY;
mSwipeEdge = swipeEdge;
mStartThresholdX = mInitTouchX;
}
void reset() {
mInitTouchX = 0;
mInitTouchY = 0;
mStartThresholdX = 0;
mCancelled = false;
mTriggerBack = false;
mSwipeEdge = BackEvent.EDGE_LEFT;
}
BackEvent createStartEvent(RemoteAnimationTarget target) {
return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
}
BackEvent 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);
}
return createProgressEvent(progress);
}
BackEvent createProgressEvent(float progress) {
return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
}
public void setProgressThreshold(float progressThreshold) {
mProgressThreshold = progressThreshold;
}
}

View File

@@ -127,6 +127,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext, mActivityTaskManager, mContext,
mContentResolver); mContentResolver);
mController.setEnableUAnimation(true);
mShellInit.init(); mShellInit.init();
mEventTime = 0; mEventTime = 0;
mShellExecutor.flushAll(); mShellExecutor.flushAll();
@@ -245,10 +246,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move. // Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100); doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted();
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
// Check that back invocation is dispatched. // Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back mController.setTriggerBack(true); // Fake trigger back
@@ -276,11 +277,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture(); triggerBackGesture();
verify(appCallback, never()).onBackStarted(); verify(appCallback, never()).onBackStarted(any(BackEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked(); verify(appCallback, times(1)).onBackInvoked();
verify(mIOnBackInvokedCallback, never()).onBackStarted(); verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked(); verify(mIOnBackInvokedCallback, never()).onBackInvoked();
} }
@@ -313,7 +314,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100); doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted(); verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
} }
@Test @Test
@@ -332,7 +333,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100); doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted(); verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
} }
@@ -348,7 +349,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move. // Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100); doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted(); verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
// Check that back invocation is dispatched. // Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back mController.setTriggerBack(true); // Fake trigger back

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.back;
import static org.junit.Assert.assertEquals;
import android.window.BackEvent;
import org.junit.Before;
import org.junit.Test;
public class TouchTrackerTest {
private static final float FAKE_THRESHOLD = 400;
private static final float INITIAL_X_LEFT_EDGE = 5;
private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
private TouchTracker mTouchTracker;
@Before
public void setUp() throws Exception {
mTouchTracker = new TouchTracker();
mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
}
@Test
public void generatesProgress_onStart() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
BackEvent event = mTouchTracker.createStartEvent(null);
assertEquals(event.getProgress(), 0f, 0f);
}
@Test
public void generatesProgress_leftEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
float touchX = 10;
// Pre-commit
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX += 100;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Cancel
touchX -= 10;
mTouchTracker.setTriggerBack(false);
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX -= 10;
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX += 10;
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX += 10;
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX += 10;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
}
@Test
public void generatesProgress_rightEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
// Pre-commit
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX -= 100;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Cancel
touchX += 10;
mTouchTracker.setTriggerBack(false);
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX += 10;
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX -= 10;
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX -= 10;
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX -= 10;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
}
private float getProgress() {
return mTouchTracker.createProgressEvent().getProgress();
}
}

View File

@@ -242,7 +242,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
private IOnBackInvokedCallback createOnBackInvokedCallback() { private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() { return new IOnBackInvokedCallback.Stub() {
@Override @Override
public void onBackStarted() { public void onBackStarted(BackEvent backEvent) {
} }
@Override @Override