diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 28c8980668..8bd72d83be 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -69,9 +69,13 @@ import android.view.HapticFeedbackConstants; import android.view.RemoteAnimationTarget; import android.view.View; import android.view.WindowManagerGlobal; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; +import android.window.OnBackInvokedDispatcher; import android.window.SplashScreen; import androidx.annotation.BinderThread; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.app.viewcapture.ViewCapture; @@ -105,6 +109,8 @@ import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.taskbar.TaskbarManager; +import com.android.launcher3.testing.TestLogging; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory; import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; @@ -623,6 +629,29 @@ public class QuickstepLauncher extends Launcher { } } + @Override + protected void registerBackDispatcher() { + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + new OnBackAnimationCallback() { + @Override + public void onBackInvoked() { + onBackPressed(); + TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked"); + } + + @Override + public void onBackProgressed(@NonNull BackEvent backEvent) { + QuickstepLauncher.this.onBackProgressed(backEvent.getProgress()); + } + + @Override + public void onBackCancelled() { + QuickstepLauncher.this.onBackCancelled(); + } + }); + } + private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) { if (mTaskbarManager == null || mTaskbarManager.getCurrentActivityContext() == null diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 61707dff49..e71391f907 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -176,14 +176,7 @@ public abstract class BaseActivity extends Activity implements ActivityContext { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (Utilities.ATLEAST_T) { - getOnBackInvokedDispatcher().registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, - () -> { - onBackPressed(); - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked"); - }); - } + registerBackDispatcher(); } @Override @@ -246,6 +239,17 @@ public abstract class BaseActivity extends Activity implements ActivityContext { } + protected void registerBackDispatcher() { + if (Utilities.ATLEAST_T) { + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + () -> { + onBackPressed(); + TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked"); + }); + } + } + public boolean isStarted() { return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 43772e4279..1563130493 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -120,6 +120,7 @@ import android.widget.ImageView; import android.widget.Toast; import androidx.annotation.CallSuper; +import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -2065,6 +2066,14 @@ public class Launcher extends StatefulActivity mStateManager.getState().onBackPressed(this); } + protected void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float backProgress) { + mStateManager.getState().onBackProgressed(this, backProgress); + } + + protected void onBackCancelled() { + mStateManager.getState().onBackCancelled(this); + } + protected void onScreenOff() { // Reset AllApps to its initial state only if we are not in the middle of // processing a multi-step drop diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 5dddc6f458..b9e4c175e2 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -34,6 +34,8 @@ import android.content.Context; import android.graphics.Color; import android.view.animation.Interpolator; +import androidx.annotation.FloatRange; + import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.states.HintState; @@ -342,6 +344,27 @@ public abstract class LauncherState implements BaseState { } } + /** + * Find {@link StateManager} and target {@link LauncherState} to handle back progress in + * predictive back gesture. + */ + public void onBackProgressed( + Launcher launcher, @FloatRange(from = 0.0, to = 1.0) float backProgress) { + StateManager lsm = launcher.getStateManager(); + LauncherState toState = lsm.getLastState(); + lsm.onBackProgressed(toState, backProgress); + } + + /** + * Find {@link StateManager} and target {@link LauncherState} to handle backProgress in + * predictive back gesture. + */ + public void onBackCancelled(Launcher launcher) { + StateManager lsm = launcher.getStateManager(); + LauncherState toState = lsm.getLastState(); + lsm.onBackCancelled(toState); + } + public static abstract class PageAlphaProvider { public final Interpolator interpolator; diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 9930abeb12..24bfedb037 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.allapps; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; @@ -33,11 +34,15 @@ import android.view.HapticFeedbackConstants; import android.view.View; import android.view.animation.Interpolator; +import androidx.annotation.FloatRange; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.AnimatorListeners; +import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.statemanager.StateManager.StateHandler; @@ -61,6 +66,8 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener { // This constant should match the second derivative of the animator interpolator. public static final float INTERP_COEFF = 1.7f; + private static final float SWIPE_ALL_APPS_TO_HOME_MIN_SCALE = 0.9f; + private static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200; public static final FloatProperty ALL_APPS_PROGRESS = new FloatProperty("allAppsProgress") { @@ -139,6 +146,7 @@ public class AllAppsTransitionController private ActivityAllAppsContainerView mAppsView; private final Launcher mLauncher; + private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged); private boolean mIsVerticalLayout; // Whether this class should take care of closing the keyboard. @@ -232,6 +240,52 @@ public class AllAppsTransitionController onProgressAnimationEnd(); } + @Override + public void onBackProgressed( + LauncherState toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) { + if (!mLauncher.isInState(ALL_APPS) || !NORMAL.equals(toState)) { + return; + } + + float deceleratedProgress = + Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(backProgress); + float scaleProgress = SWIPE_ALL_APPS_TO_HOME_MIN_SCALE + + (1 - SWIPE_ALL_APPS_TO_HOME_MIN_SCALE) * (1 - deceleratedProgress); + + mAllAppScale.updateValue(scaleProgress); + } + + @Override + public void onBackCancelled(LauncherState toState) { + if (!mLauncher.isInState(ALL_APPS) || !NORMAL.equals(toState)) { + return; + } + + // TODO: once ag/20649618 is picked into tm-qpr, we don't need to animate back on cancel + // swipe because framework will do that for us in {@link #onBackProgressed}. + animateAllAppsToNoScale(); + } + + private void onScaleProgressChanged() { + final float scaleProgress = mAllAppScale.value; + SCALE_PROPERTY.set(mLauncher.getAppsView(), scaleProgress); + mLauncher.getScrimView().setScrimHeaderScale(scaleProgress); + + AllAppsRecyclerView rv = mLauncher.getAppsView().getActiveRecyclerView(); + if (rv != null && rv.getScrollbar() != null) { + rv.getScrollbar().setVisibility(scaleProgress < 1f ? View.INVISIBLE : View.VISIBLE); + } + + // TODO(b/264906511): We need to disable view clipping on all apps' parent views so + // that the extra roll of app icons are displayed. + } + + private void animateAllAppsToNoScale() { + mAllAppScale.animateToValue(1f) + .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS) + .start(); + } + /** * Creates an animation which updates the vertical transition progress and updates all the * dependent UI using various animation events @@ -258,6 +312,8 @@ public class AllAppsTransitionController if (config.userControlled && success && mShouldControlKeyboard) { mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard(); } + + mAllAppScale.updateValue(1f); }); } diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java index 00e89bacc5..487807751d 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java @@ -808,7 +808,7 @@ public abstract class BaseAllAppsContainerView> { } } + /** Handles backProgress in predictive back gesture by passing it to state handlers. */ + public void onBackProgressed( + STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) { + for (StateHandler handler : getStateHandlers()) { + handler.onBackProgressed(toState, backProgress); + } + } + + /** Handles back cancelled event in predictive back gesture by passing it to state handlers. */ + public void onBackCancelled(STATE_TYPE toState) { + for (StateHandler handler : getStateHandlers()) { + handler.onBackCancelled(toState); + } + } + private void goToState( STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) { animated &= areAnimatorsEnabled(); @@ -586,6 +603,13 @@ public class StateManager> { */ void setStateWithAnimation( STATE_TYPE toState, StateAnimationConfig config, PendingAnimation animation); + + /** Handles backProgress in predictive back gesture for target state. */ + default void onBackProgressed( + STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}; + + /** Handles back cancelled event in predictive back gesture for target state. */ + default void onBackCancelled(STATE_TYPE toState) {}; } public interface StateListener { diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 4c0bfde682..870ff122e1 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -46,6 +46,7 @@ public class ScrimView extends View implements Insettable { private int mBackgroundColor; private boolean mIsVisible = true; private boolean mLastDispatchedOpaqueness; + private float mHeaderScale = 1f; public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); @@ -91,7 +92,16 @@ public class ScrimView extends View implements Insettable { protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mDrawingController != null) { - mDrawingController.drawOnScrim(canvas); + mDrawingController.drawOnScrimWithScale(canvas, mHeaderScale); + } + } + + /** Set scrim header's scale and bottom offset. */ + public void setScrimHeaderScale(float scale) { + boolean hasChanged = mHeaderScale != scale; + mHeaderScale = scale; + if (hasChanged) { + invalidate(); } } @@ -176,6 +186,6 @@ public class ScrimView extends View implements Insettable { /** * Called inside ScrimView#OnDraw */ - void drawOnScrim(Canvas canvas); + void drawOnScrimWithScale(Canvas canvas, float scale); } }