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:
136
core/java/android/window/BackProgressAnimator.java
Normal file
136
core/java/android/window/BackProgressAnimator.java
Normal 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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,17 +28,18 @@ import android.window.BackEvent;
|
||||
oneway interface IOnBackInvokedCallback {
|
||||
/**
|
||||
* 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.
|
||||
* Wraps {@link OnBackInvokedCallback#onBackProgressed()}.
|
||||
* Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
|
||||
*
|
||||
* @param touchX Absolute X location of the touch point.
|
||||
* @param touchY Absolute Y location of the touch point.
|
||||
* @param progress Value between 0 and 1 on how far along the back gesture is.
|
||||
* @param backEvent The {@link BackEvent} containing information about the latest touch point
|
||||
* and the progress that the back animation should seek to.
|
||||
*/
|
||||
void onBackProgressed(in BackEvent backEvent);
|
||||
|
||||
|
||||
@@ -40,10 +40,12 @@ import android.view.View;
|
||||
* @hide
|
||||
*/
|
||||
public interface OnBackAnimationCallback extends OnBackInvokedCallback {
|
||||
/**
|
||||
* Called when a back gesture has been started, or back button has been pressed down.
|
||||
*/
|
||||
default void onBackStarted() { }
|
||||
/**
|
||||
* Called when a back gesture has been started, or back button has been pressed down.
|
||||
*
|
||||
* @param backEvent An {@link BackEvent} object describing the progress event.
|
||||
*/
|
||||
default void onBackStarted(@NonNull BackEvent backEvent) {}
|
||||
|
||||
/**
|
||||
* Called on back gesture progress.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.window;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.view.Window;
|
||||
@@ -40,9 +41,36 @@ import android.view.Window;
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
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
|
||||
* has been released and committed.
|
||||
*/
|
||||
void onBackInvoked();
|
||||
|
||||
/**
|
||||
* Called when a back gesture or button press has been cancelled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
default void onBackCancelled() {}
|
||||
}
|
||||
|
||||
@@ -218,19 +218,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
|
||||
public Checker getChecker() {
|
||||
return mChecker;
|
||||
}
|
||||
@NonNull
|
||||
private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
|
||||
|
||||
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
|
||||
private final WeakReference<OnBackInvokedCallback> mCallback;
|
||||
|
||||
OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
|
||||
mCallback = new WeakReference<>(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStarted() {
|
||||
public void onBackStarted(BackEvent backEvent) {
|
||||
Handler.getMain().post(() -> {
|
||||
final OnBackAnimationCallback callback = getBackAnimationCallback();
|
||||
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(() -> {
|
||||
final OnBackAnimationCallback callback = getBackAnimationCallback();
|
||||
if (callback != null) {
|
||||
callback.onBackProgressed(backEvent);
|
||||
mProgressAnimator.onBackProgressed(backEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -248,6 +253,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
|
||||
@Override
|
||||
public void onBackCancelled() {
|
||||
Handler.getMain().post(() -> {
|
||||
mProgressAnimator.reset();
|
||||
final OnBackAnimationCallback callback = getBackAnimationCallback();
|
||||
if (callback != null) {
|
||||
callback.onBackCancelled();
|
||||
@@ -258,6 +264,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
|
||||
@Override
|
||||
public void onBackInvoked() throws RemoteException {
|
||||
Handler.getMain().post(() -> {
|
||||
mProgressAnimator.reset();
|
||||
final OnBackInvokedCallback callback = mCallback.get();
|
||||
if (callback == null) {
|
||||
return;
|
||||
|
||||
@@ -60,6 +60,8 @@ public class WindowOnBackInvokedDispatcherTest {
|
||||
private OnBackAnimationCallback mCallback1;
|
||||
@Mock
|
||||
private OnBackAnimationCallback mCallback2;
|
||||
@Mock
|
||||
private BackEvent mBackEvent;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -85,14 +87,14 @@ public class WindowOnBackInvokedDispatcherTest {
|
||||
verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
|
||||
Mockito.eq(mWindow),
|
||||
captor.capture());
|
||||
captor.getAllValues().get(0).getCallback().onBackStarted();
|
||||
captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
|
||||
waitForIdle();
|
||||
verify(mCallback1).onBackStarted();
|
||||
verify(mCallback1).onBackStarted(mBackEvent);
|
||||
verifyZeroInteractions(mCallback2);
|
||||
|
||||
captor.getAllValues().get(1).getCallback().onBackStarted();
|
||||
captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
|
||||
waitForIdle();
|
||||
verify(mCallback2).onBackStarted();
|
||||
verify(mCallback2).onBackStarted(mBackEvent);
|
||||
verifyNoMoreInteractions(mCallback1);
|
||||
}
|
||||
|
||||
@@ -110,9 +112,9 @@ public class WindowOnBackInvokedDispatcherTest {
|
||||
Mockito.eq(mWindow), captor.capture());
|
||||
verifyNoMoreInteractions(mWindowSession);
|
||||
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
|
||||
captor.getValue().getCallback().onBackStarted();
|
||||
captor.getValue().getCallback().onBackStarted(mBackEvent);
|
||||
waitForIdle();
|
||||
verify(mCallback1).onBackStarted();
|
||||
verify(mCallback1).onBackStarted(mBackEvent);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,8 +150,8 @@ public class WindowOnBackInvokedDispatcherTest {
|
||||
mDispatcher.registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
|
||||
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
|
||||
captor.getValue().getCallback().onBackStarted();
|
||||
captor.getValue().getCallback().onBackStarted(mBackEvent);
|
||||
waitForIdle();
|
||||
verify(mCallback2).onBackStarted();
|
||||
verify(mCallback2).onBackStarted(mBackEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,13 +75,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
private static final String TAG = "BackAnimationController";
|
||||
private static final int SETTING_VALUE_OFF = 0;
|
||||
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 =
|
||||
SystemProperties.getInt("persist.wm.debug.predictive_back",
|
||||
SETTING_VALUE_ON) != SETTING_VALUE_OFF;
|
||||
private static final int PROGRESS_THRESHOLD = SystemProperties
|
||||
.getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
|
||||
SETTING_VALUE_ON) == SETTING_VALUE_ON;
|
||||
/** Flag for U animation features */
|
||||
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);
|
||||
// TODO (b/241808055) Find a appropriate time to remove during refactor
|
||||
private static final boolean USE_TRANSITION =
|
||||
@@ -114,7 +115,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
@Nullable
|
||||
private IOnBackInvokedCallback mBackToLauncherCallback;
|
||||
private float mTriggerThreshold;
|
||||
private float mProgressThreshold;
|
||||
private final Runnable mResetTransitionRunnable = () -> {
|
||||
finishAnimation();
|
||||
mTransitionInProgress = false;
|
||||
@@ -125,7 +125,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
private IBackNaviAnimationController mBackAnimationController;
|
||||
private BackAnimationAdaptor mBackAnimationAdaptor;
|
||||
|
||||
private boolean mWaitingAnimationStart;
|
||||
private final TouchTracker mTouchTracker = new TouchTracker();
|
||||
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
|
||||
* BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
|
||||
@@ -212,15 +173,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
boolean consumed = false;
|
||||
if (mWaitingAnimation && mOnBackCallback != null) {
|
||||
if (mTriggerBack) {
|
||||
final BackEvent backFinish = new BackEvent(
|
||||
mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
|
||||
mTouchTracker.mSwipeEdge, mAnimationTarget);
|
||||
final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
|
||||
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
|
||||
dispatchOnBackInvoked(mOnBackCallback);
|
||||
} else {
|
||||
final BackEvent backFinish = new BackEvent(
|
||||
mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
|
||||
mTouchTracker.mSwipeEdge, mAnimationTarget);
|
||||
final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
|
||||
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
|
||||
dispatchOnBackCancelled(mOnBackCallback);
|
||||
}
|
||||
@@ -263,6 +220,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
shellInit.addInitCallback(this::onInit, this);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setEnableUAnimation(boolean enable) {
|
||||
IS_U_ANIMATION_ENABLED = enable;
|
||||
}
|
||||
|
||||
private void onInit() {
|
||||
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
|
||||
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
|
||||
@@ -404,7 +366,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
return;
|
||||
}
|
||||
|
||||
mTouchTracker.update(touchX, touchY, swipeEdge);
|
||||
mTouchTracker.update(touchX, touchY);
|
||||
if (keyAction == MotionEvent.ACTION_DOWN) {
|
||||
if (!mBackGestureStarted) {
|
||||
mShouldStartOnNextMoveEvent = true;
|
||||
@@ -414,7 +376,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
|
||||
// could be happened when ACTION_DOWN, it may change the current focus that we
|
||||
// would access it when startBackNavigation.
|
||||
onGestureStarted(touchX, touchY);
|
||||
onGestureStarted(touchX, touchY, swipeEdge);
|
||||
mShouldStartOnNextMoveEvent = false;
|
||||
}
|
||||
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);
|
||||
if (mBackGestureStarted || mBackNavigationInfo != null) {
|
||||
Log.e(TAG, "Animation is being initialized but is already started.");
|
||||
finishAnimation();
|
||||
}
|
||||
|
||||
mTouchTracker.setGestureStartLocation(touchX, touchY);
|
||||
mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
|
||||
mBackGestureStarted = true;
|
||||
|
||||
try {
|
||||
@@ -464,6 +426,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
displayTargetScreenshot(hardwareBuffer,
|
||||
backNavigationInfo.getTaskWindowConfiguration());
|
||||
}
|
||||
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
|
||||
mTransaction.apply();
|
||||
} else if (dispatchToLauncher) {
|
||||
targetCallback = mBackToLauncherCallback;
|
||||
@@ -474,7 +437,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
|
||||
}
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX);
|
||||
float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
|
||||
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);
|
||||
final BackEvent backEvent = mTouchTracker.createProgressEvent();
|
||||
if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
|
||||
dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
|
||||
}
|
||||
} else {
|
||||
} else if (mEnableAnimations.get()) {
|
||||
int backType = mBackNavigationInfo.getType();
|
||||
RemoteAnimationTarget animationTarget =
|
||||
mBackNavigationInfo.getDepartingAnimationTarget();
|
||||
|
||||
BackEvent backEvent = new BackEvent(
|
||||
touchX, touchY, progress, swipeEdge, animationTarget);
|
||||
IOnBackInvokedCallback targetCallback = null;
|
||||
IOnBackInvokedCallback targetCallback;
|
||||
if (shouldDispatchToLauncher(backType)) {
|
||||
targetCallback = mBackToLauncherCallback;
|
||||
} else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
|
||||
|| backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
|
||||
// TODO(208427216) Run the actual animation
|
||||
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
|
||||
} else {
|
||||
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
|
||||
}
|
||||
dispatchOnBackProgressed(targetCallback, backEvent);
|
||||
@@ -620,18 +572,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
|| mBackNavigationInfo.getDepartingAnimationTarget() != null);
|
||||
}
|
||||
|
||||
private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
|
||||
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
|
||||
BackEvent backEvent) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback.onBackStarted();
|
||||
if (shouldDispatchAnimation(callback)) {
|
||||
callback.onBackStarted(backEvent);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "dispatchOnBackStarted error: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
|
||||
private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
|
||||
if (callback == null) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback.onBackCancelled();
|
||||
if (shouldDispatchAnimation(callback)) {
|
||||
callback.onBackCancelled();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
|
||||
private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
|
||||
BackEvent backEvent) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback.onBackProgressed(backEvent);
|
||||
if (shouldDispatchAnimation(callback)) {
|
||||
callback.onBackProgressed(backEvent);
|
||||
}
|
||||
} catch (RemoteException 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.
|
||||
*/
|
||||
@@ -673,10 +637,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
return;
|
||||
}
|
||||
mTriggerBack = triggerBack;
|
||||
mTouchTracker.setTriggerBack(triggerBack);
|
||||
}
|
||||
|
||||
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
|
||||
mProgressThreshold = progressThreshold;
|
||||
mTouchTracker.setProgressThreshold(progressThreshold);
|
||||
mTriggerThreshold = triggerThreshold;
|
||||
}
|
||||
|
||||
@@ -686,6 +651,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
|
||||
boolean triggerBack = mTriggerBack;
|
||||
mBackNavigationInfo = null;
|
||||
mAnimationTarget = null;
|
||||
mTriggerBack = false;
|
||||
mShouldStartOnNextMoveEvent = false;
|
||||
if (backNavigationInfo == null) {
|
||||
@@ -762,17 +728,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
tx.apply();
|
||||
}
|
||||
}
|
||||
// TODO animation target should be passed at onBackStarted
|
||||
dispatchOnBackStarted(mBackToLauncherCallback);
|
||||
// TODO This is Workaround for LauncherBackAnimationController, there will need
|
||||
// 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);
|
||||
dispatchOnBackStarted(mBackToLauncherCallback,
|
||||
mTouchTracker.createStartEvent(mAnimationTarget));
|
||||
final BackEvent backInit = mTouchTracker.createProgressEvent();
|
||||
if (!mCachingBackDispatcher.consume()) {
|
||||
dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
|
||||
mActivityTaskManager, mContext,
|
||||
mContentResolver);
|
||||
mController.setEnableUAnimation(true);
|
||||
mShellInit.init();
|
||||
mEventTime = 0;
|
||||
mShellExecutor.flushAll();
|
||||
@@ -245,10 +246,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
// Check that back start and progress is dispatched when first move.
|
||||
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
|
||||
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
|
||||
verify(mIOnBackInvokedCallback).onBackStarted();
|
||||
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
|
||||
verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
|
||||
verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
|
||||
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
|
||||
verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
|
||||
|
||||
// Check that back invocation is dispatched.
|
||||
mController.setTriggerBack(true); // Fake trigger back
|
||||
@@ -276,11 +277,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
|
||||
triggerBackGesture();
|
||||
|
||||
verify(appCallback, never()).onBackStarted();
|
||||
verify(appCallback, never()).onBackStarted(any(BackEvent.class));
|
||||
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
|
||||
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()).onBackInvoked();
|
||||
}
|
||||
@@ -313,7 +314,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
|
||||
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
|
||||
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
|
||||
verify(mIOnBackInvokedCallback).onBackStarted();
|
||||
verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -332,7 +333,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
|
||||
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
|
||||
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.
|
||||
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
|
||||
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
|
||||
verify(mIOnBackInvokedCallback).onBackStarted();
|
||||
verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
|
||||
|
||||
// Check that back invocation is dispatched.
|
||||
mController.setTriggerBack(true); // Fake trigger back
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
|
||||
private IOnBackInvokedCallback createOnBackInvokedCallback() {
|
||||
return new IOnBackInvokedCallback.Stub() {
|
||||
@Override
|
||||
public void onBackStarted() {
|
||||
public void onBackStarted(BackEvent backEvent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user