Loading libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +97 −2 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager; Loading @@ -37,7 +40,9 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserHandle; import android.provider.Settings.Global; import android.provider.Settings.Global; import android.util.DisplayMetrics; import android.util.Log; import android.util.Log; import android.util.MathUtils; import android.util.SparseArray; import android.util.SparseArray; import android.view.IRemoteAnimationRunner; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; import android.view.InputDevice; Loading @@ -56,6 +61,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.view.AppearanceRegion; import com.android.internal.view.AppearanceRegion; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor; Loading @@ -80,6 +86,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont public static boolean IS_U_ANIMATION_ENABLED = public static boolean IS_U_ANIMATION_ENABLED = SystemProperties.getInt("persist.wm.debug.predictive_back_anim", SystemProperties.getInt("persist.wm.debug.predictive_back_anim", SETTING_VALUE_ON) == SETTING_VALUE_ON; SETTING_VALUE_ON) == SETTING_VALUE_ON; public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms public static final float FLING_SPEED_UP_FACTOR = 0.6f; /** * The maximum additional progress in case of fling gesture. * The end animation starts after the user lifts the finger from the screen, we continue to * fire {@link BackEvent}s until the velocity reaches 0. */ private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */ /** Predictive back animation developer option */ /** Predictive back animation developer option */ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); /** /** Loading @@ -96,6 +113,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mShouldStartOnNextMoveEvent = false; private boolean mShouldStartOnNextMoveEvent = false; /** @see #setTriggerBack(boolean) */ /** @see #setTriggerBack(boolean) */ private boolean mTriggerBack; private boolean mTriggerBack; private FlingAnimationUtils mFlingAnimationUtils; @Nullable @Nullable private BackNavigationInfo mBackNavigationInfo; private BackNavigationInfo mBackNavigationInfo; Loading Loading @@ -174,6 +192,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBgHandler = bgHandler; mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; mAnimationBackground = backAnimationBackground; DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics) .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) .build(); } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -465,6 +488,78 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } } } /** * Allows us to manage the fling gesture, it smoothly animates the current progress value to * the final position, calculated based on the current velocity. * * @param callback the callback to be invoked when the animation ends. */ private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { return; } boolean animationStarted = false; if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) { final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent(); if (backMotionEvent != null) { // Constraints - absolute values float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond(); float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond(); float maxX = mTouchTracker.getMaxX(); // px float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px // Current state float currentX = backMotionEvent.getTouchX(); float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(), -maxVelocity, maxVelocity); // Target state float animationFaction = velocity / maxVelocity; // value between -1 and 1 float flingDistance = animationFaction * maxFlingDistance; // px float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX); if (!Float.isNaN(endX) && currentX != endX && Math.abs(velocity) >= minVelocity) { ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX); mFlingAnimationUtils.apply( /* animator = */ animator, /* currValue = */ currentX, /* endValue = */ endX, /* velocity = */ velocity, /* maxDistance = */ maxFlingDistance ); animator.addUpdateListener(animation -> { Float animatedValue = (Float) animation.getAnimatedValue(); float progress = mTouchTracker.getProgress(animatedValue); final BackMotionEvent backEvent = mTouchTracker .createProgressEvent(progress); dispatchOnBackProgressed(mActiveCallback, backEvent); }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { dispatchOnBackInvoked(callback); } }); animator.start(); animationStarted = true; } } } if (!animationStarted) { dispatchOnBackInvoked(callback); } } private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { if (callback == null) { return; return; Loading Loading @@ -530,7 +625,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mBackNavigationInfo != null) { if (mBackNavigationInfo != null) { final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (mTriggerBack) { if (mTriggerBack) { dispatchOnBackInvoked(callback); dispatchOrAnimateOnBackInvoked(callback); } else { } else { dispatchOnBackCancelled(callback); dispatchOnBackCancelled(callback); } } Loading Loading @@ -605,7 +700,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // The next callback should be {@link #onBackAnimationFinished}. // The next callback should be {@link #onBackAnimationFinished}. if (mTriggerBack) { if (mTriggerBack) { dispatchOnBackInvoked(mActiveCallback); dispatchOrAnimateOnBackInvoked(mActiveCallback); } else { } else { dispatchOnBackCancelled(mActiveCallback); dispatchOnBackCancelled(mActiveCallback); } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +32 −15 Original line number Original line Diff line number Diff line Loading @@ -16,7 +16,10 @@ package com.android.wm.shell.back; package com.android.wm.shell.back; import android.annotation.FloatRange; import android.os.SystemProperties; import android.os.SystemProperties; import android.util.MathUtils; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.RemoteAnimationTarget; import android.window.BackEvent; import android.window.BackEvent; import android.window.BackMotionEvent; import android.window.BackMotionEvent; Loading Loading @@ -99,26 +102,40 @@ class TouchTracker { } } BackMotionEvent createProgressEvent() { BackMotionEvent createProgressEvent() { float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; progressThreshold = progressThreshold == 0 ? 1 : progressThreshold; float progress = 0; float progress = 0; // Progress is always 0 when back is cancelled and not restarted. // Progress is always 0 when back is cancelled and not restarted. if (!mCancelled) { if (!mCancelled) { progress = getProgress(mLatestTouchX); } return createProgressEvent(progress); } /** * Progress value computed from the touch position. * * @param touchX the X touch position of the {@link MotionEvent}. * @return progress value */ @FloatRange(from = 0.0, to = 1.0) float getProgress(float touchX) { // If back is committed, progress is the distance between the last and first touch // 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 // 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 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 starting threshold is initially the first touch location, and updated to // the location everytime back is restarted after being cancelled. // the location everytime back is restarted after being cancelled. float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; float deltaX = Math.max( float deltaX = Math.abs(startX - touchX); mSwipeEdge == BackEvent.EDGE_LEFT float maxX = getMaxX(); ? mLatestTouchX - startX maxX = maxX == 0 ? 1 : maxX; : startX - mLatestTouchX, return MathUtils.constrain(deltaX / maxX, 0, 1); 0); progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1); } } return createProgressEvent(progress); /** * Maximum X value (in pixels). * Progress is considered to be completed (1f) when this limit is exceeded. */ float getMaxX() { return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; } } BackMotionEvent createProgressEvent(float progress) { BackMotionEvent createProgressEvent(float progress) { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +55 −10 Original line number Original line Diff line number Diff line Loading @@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); mShellExecutor.flushAll(); } } private void createNavigationInfo(int backType, boolean enableAnimation) { private void createNavigationInfo(int backType, boolean enableAnimation, boolean isAnimationCallback) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) .setType(backType) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) .setOnBackInvokedCallback(mAppCallback) .setOnBackInvokedCallback(mAppCallback) .setPrepareRemoteAnimation(enableAnimation); .setPrepareRemoteAnimation(enableAnimation) .setAnimationCallback(isAnimationCallback); createNavigationInfo(builder); createNavigationInfo(builder); } } Loading Loading @@ -218,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test @Test public void backToHome_dispatchesEvents() throws RemoteException { public void backToHome_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0); Loading @@ -239,6 +244,32 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mAnimatorCallback).onBackInvoked(); verify(mAnimatorCallback).onBackInvoked(); } } @Test public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class)); verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back doMotionEvent(MotionEvent.ACTION_UP, 0); verify(mAnimatorCallback).onBackInvoked(); } @Test @Test public void animationDisabledFromSettings() throws RemoteException { public void animationDisabledFromSettings() throws RemoteException { // Toggle the setting off // Toggle the setting off Loading @@ -254,7 +285,9 @@ public class BackAnimationControllerTest extends ShellTestCase { ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); ArgumentCaptor.forClass(BackMotionEvent.class); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ false, /* isAnimationCallback = */ false); triggerBackGesture(); triggerBackGesture(); releaseBackGesture(); releaseBackGesture(); Loading @@ -271,7 +304,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test @Test public void ignoresGesture_transitionInProgress() throws RemoteException { public void ignoresGesture_transitionInProgress() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); triggerBackGesture(); triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); Loading Loading @@ -309,7 +344,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test @Test public void acceptsGesture_transitionTimeout() throws RemoteException { public void acceptsGesture_transitionTimeout() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); // In case it is still running in animation. // In case it is still running in animation. doNothing().when(mAnimatorCallback).onBackInvoked(); doNothing().when(mAnimatorCallback).onBackInvoked(); Loading @@ -334,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase { public void cancelBackInvokeWhenLostFocus() throws RemoteException { public void cancelBackInvokeWhenLostFocus() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. // Check that back start and progress is dispatched when first move. Loading Loading @@ -454,7 +493,9 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.registerAnimation(type, animationRunner); mController.registerAnimation(type, animationRunner); createNavigationInfo(type, true); createNavigationInfo(type, /* enableAnimation = */ true, /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0); Loading @@ -473,11 +514,15 @@ public class BackAnimationControllerTest extends ShellTestCase { } } private void doMotionEvent(int actionDown, int coordinate) { private void doMotionEvent(int actionDown, int coordinate) { doMotionEvent(actionDown, coordinate, 0); } private void doMotionEvent(int actionDown, int coordinate, float velocity) { mController.onMotionEvent( mController.onMotionEvent( /* touchX */ coordinate, /* touchX */ coordinate, /* touchY */ coordinate, /* touchY */ coordinate, /* velocityX = */ 0, /* velocityX = */ velocity, /* velocityY = */ 0, /* velocityY = */ velocity, /* keyAction */ actionDown, /* keyAction */ actionDown, /* swipeEdge */ BackEvent.EDGE_LEFT); /* swipeEdge */ BackEvent.EDGE_LEFT); } } Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +97 −2 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager; Loading @@ -37,7 +40,9 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserHandle; import android.provider.Settings.Global; import android.provider.Settings.Global; import android.util.DisplayMetrics; import android.util.Log; import android.util.Log; import android.util.MathUtils; import android.util.SparseArray; import android.util.SparseArray; import android.view.IRemoteAnimationRunner; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; import android.view.InputDevice; Loading @@ -56,6 +61,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.view.AppearanceRegion; import com.android.internal.view.AppearanceRegion; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor; Loading @@ -80,6 +86,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont public static boolean IS_U_ANIMATION_ENABLED = public static boolean IS_U_ANIMATION_ENABLED = SystemProperties.getInt("persist.wm.debug.predictive_back_anim", SystemProperties.getInt("persist.wm.debug.predictive_back_anim", SETTING_VALUE_ON) == SETTING_VALUE_ON; SETTING_VALUE_ON) == SETTING_VALUE_ON; public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms public static final float FLING_SPEED_UP_FACTOR = 0.6f; /** * The maximum additional progress in case of fling gesture. * The end animation starts after the user lifts the finger from the screen, we continue to * fire {@link BackEvent}s until the velocity reaches 0. */ private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */ /** Predictive back animation developer option */ /** Predictive back animation developer option */ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); /** /** Loading @@ -96,6 +113,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mShouldStartOnNextMoveEvent = false; private boolean mShouldStartOnNextMoveEvent = false; /** @see #setTriggerBack(boolean) */ /** @see #setTriggerBack(boolean) */ private boolean mTriggerBack; private boolean mTriggerBack; private FlingAnimationUtils mFlingAnimationUtils; @Nullable @Nullable private BackNavigationInfo mBackNavigationInfo; private BackNavigationInfo mBackNavigationInfo; Loading Loading @@ -174,6 +192,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBgHandler = bgHandler; mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; mAnimationBackground = backAnimationBackground; DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics) .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) .build(); } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -465,6 +488,78 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } } } /** * Allows us to manage the fling gesture, it smoothly animates the current progress value to * the final position, calculated based on the current velocity. * * @param callback the callback to be invoked when the animation ends. */ private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { return; } boolean animationStarted = false; if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) { final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent(); if (backMotionEvent != null) { // Constraints - absolute values float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond(); float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond(); float maxX = mTouchTracker.getMaxX(); // px float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px // Current state float currentX = backMotionEvent.getTouchX(); float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(), -maxVelocity, maxVelocity); // Target state float animationFaction = velocity / maxVelocity; // value between -1 and 1 float flingDistance = animationFaction * maxFlingDistance; // px float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX); if (!Float.isNaN(endX) && currentX != endX && Math.abs(velocity) >= minVelocity) { ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX); mFlingAnimationUtils.apply( /* animator = */ animator, /* currValue = */ currentX, /* endValue = */ endX, /* velocity = */ velocity, /* maxDistance = */ maxFlingDistance ); animator.addUpdateListener(animation -> { Float animatedValue = (Float) animation.getAnimatedValue(); float progress = mTouchTracker.getProgress(animatedValue); final BackMotionEvent backEvent = mTouchTracker .createProgressEvent(progress); dispatchOnBackProgressed(mActiveCallback, backEvent); }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { dispatchOnBackInvoked(callback); } }); animator.start(); animationStarted = true; } } } if (!animationStarted) { dispatchOnBackInvoked(callback); } } private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { if (callback == null) { return; return; Loading Loading @@ -530,7 +625,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mBackNavigationInfo != null) { if (mBackNavigationInfo != null) { final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (mTriggerBack) { if (mTriggerBack) { dispatchOnBackInvoked(callback); dispatchOrAnimateOnBackInvoked(callback); } else { } else { dispatchOnBackCancelled(callback); dispatchOnBackCancelled(callback); } } Loading Loading @@ -605,7 +700,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // The next callback should be {@link #onBackAnimationFinished}. // The next callback should be {@link #onBackAnimationFinished}. if (mTriggerBack) { if (mTriggerBack) { dispatchOnBackInvoked(mActiveCallback); dispatchOrAnimateOnBackInvoked(mActiveCallback); } else { } else { dispatchOnBackCancelled(mActiveCallback); dispatchOnBackCancelled(mActiveCallback); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +32 −15 Original line number Original line Diff line number Diff line Loading @@ -16,7 +16,10 @@ package com.android.wm.shell.back; package com.android.wm.shell.back; import android.annotation.FloatRange; import android.os.SystemProperties; import android.os.SystemProperties; import android.util.MathUtils; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.RemoteAnimationTarget; import android.window.BackEvent; import android.window.BackEvent; import android.window.BackMotionEvent; import android.window.BackMotionEvent; Loading Loading @@ -99,26 +102,40 @@ class TouchTracker { } } BackMotionEvent createProgressEvent() { BackMotionEvent createProgressEvent() { float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; progressThreshold = progressThreshold == 0 ? 1 : progressThreshold; float progress = 0; float progress = 0; // Progress is always 0 when back is cancelled and not restarted. // Progress is always 0 when back is cancelled and not restarted. if (!mCancelled) { if (!mCancelled) { progress = getProgress(mLatestTouchX); } return createProgressEvent(progress); } /** * Progress value computed from the touch position. * * @param touchX the X touch position of the {@link MotionEvent}. * @return progress value */ @FloatRange(from = 0.0, to = 1.0) float getProgress(float touchX) { // If back is committed, progress is the distance between the last and first touch // 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 // 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 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 starting threshold is initially the first touch location, and updated to // the location everytime back is restarted after being cancelled. // the location everytime back is restarted after being cancelled. float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; float deltaX = Math.max( float deltaX = Math.abs(startX - touchX); mSwipeEdge == BackEvent.EDGE_LEFT float maxX = getMaxX(); ? mLatestTouchX - startX maxX = maxX == 0 ? 1 : maxX; : startX - mLatestTouchX, return MathUtils.constrain(deltaX / maxX, 0, 1); 0); progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1); } } return createProgressEvent(progress); /** * Maximum X value (in pixels). * Progress is considered to be completed (1f) when this limit is exceeded. */ float getMaxX() { return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; } } BackMotionEvent createProgressEvent(float progress) { BackMotionEvent createProgressEvent(float progress) { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +55 −10 Original line number Original line Diff line number Diff line Loading @@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); mShellExecutor.flushAll(); } } private void createNavigationInfo(int backType, boolean enableAnimation) { private void createNavigationInfo(int backType, boolean enableAnimation, boolean isAnimationCallback) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) .setType(backType) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) .setOnBackInvokedCallback(mAppCallback) .setOnBackInvokedCallback(mAppCallback) .setPrepareRemoteAnimation(enableAnimation); .setPrepareRemoteAnimation(enableAnimation) .setAnimationCallback(isAnimationCallback); createNavigationInfo(builder); createNavigationInfo(builder); } } Loading Loading @@ -218,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test @Test public void backToHome_dispatchesEvents() throws RemoteException { public void backToHome_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0); Loading @@ -239,6 +244,32 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mAnimatorCallback).onBackInvoked(); verify(mAnimatorCallback).onBackInvoked(); } } @Test public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class)); verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back doMotionEvent(MotionEvent.ACTION_UP, 0); verify(mAnimatorCallback).onBackInvoked(); } @Test @Test public void animationDisabledFromSettings() throws RemoteException { public void animationDisabledFromSettings() throws RemoteException { // Toggle the setting off // Toggle the setting off Loading @@ -254,7 +285,9 @@ public class BackAnimationControllerTest extends ShellTestCase { ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); ArgumentCaptor.forClass(BackMotionEvent.class); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ false, /* isAnimationCallback = */ false); triggerBackGesture(); triggerBackGesture(); releaseBackGesture(); releaseBackGesture(); Loading @@ -271,7 +304,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test @Test public void ignoresGesture_transitionInProgress() throws RemoteException { public void ignoresGesture_transitionInProgress() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); triggerBackGesture(); triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); Loading Loading @@ -309,7 +344,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test @Test public void acceptsGesture_transitionTimeout() throws RemoteException { public void acceptsGesture_transitionTimeout() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); // In case it is still running in animation. // In case it is still running in animation. doNothing().when(mAnimatorCallback).onBackInvoked(); doNothing().when(mAnimatorCallback).onBackInvoked(); Loading @@ -334,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase { public void cancelBackInvokeWhenLostFocus() throws RemoteException { public void cancelBackInvokeWhenLostFocus() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, /* enableAnimation = */ true, /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. // Check that back start and progress is dispatched when first move. Loading Loading @@ -454,7 +493,9 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.registerAnimation(type, animationRunner); mController.registerAnimation(type, animationRunner); createNavigationInfo(type, true); createNavigationInfo(type, /* enableAnimation = */ true, /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_DOWN, 0); Loading @@ -473,11 +514,15 @@ public class BackAnimationControllerTest extends ShellTestCase { } } private void doMotionEvent(int actionDown, int coordinate) { private void doMotionEvent(int actionDown, int coordinate) { doMotionEvent(actionDown, coordinate, 0); } private void doMotionEvent(int actionDown, int coordinate, float velocity) { mController.onMotionEvent( mController.onMotionEvent( /* touchX */ coordinate, /* touchX */ coordinate, /* touchY */ coordinate, /* touchY */ coordinate, /* velocityX = */ 0, /* velocityX = */ velocity, /* velocityY = */ 0, /* velocityY = */ velocity, /* keyAction */ actionDown, /* keyAction */ actionDown, /* swipeEdge */ BackEvent.EDGE_LEFT); /* swipeEdge */ BackEvent.EDGE_LEFT); } } Loading