DO NOT MERGE Spring controlled back progress API
This CL introduces BackProgressAnimator which runs in app's main thread. It receives target progress values from SysUI and drives the actual progress value passed to the app with a high stiffness, no bounce spring. Bug: 238475284 Test: atest WindowOnBackDispatcherTest Test: atest BackAnimationControllerTest Test: atest TouchTrackerTest Change-Id: Ib0d3ebe43929c405b10681000fb4e7ef8bccce34
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 {
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
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
|
||||||
|
|||||||
@@ -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() {
|
private IOnBackInvokedCallback createOnBackInvokedCallback() {
|
||||||
return new IOnBackInvokedCallback.Stub() {
|
return new IOnBackInvokedCallback.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void onBackStarted() {
|
public void onBackStarted(BackEvent backEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user