Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4fdba14c authored by Tony Wickham's avatar Tony Wickham
Browse files

Two-zone model: swipe up from nav bar vs above it

When ENABLE_OVERVIEW_ACTIONS flag is enabled, swiping up from the nav
bar goes to overview if you hold, or the first home screen if you don't.

- Added NoButtonNavBarToOverviewTouchController, which extends
  FlingAndHoldTouchController but only works if you start from the nav
  bar. Otherwise it falls back to PortraitStatesTouchController to
  handle normal state transition to all apps.
- Added HintState. This is where the workspace/hotseat/qsb scale down
  when you swipe up from the nav bar, to hint that you're about to
  either go to overview or the first home screen.
  - Added getQsbScaleAndTranslation() to allow Overview and Hint states
    to treat it as part of the hotseat.
  - Since Overview needs to show above the QSB as it's animating, bring
    Overview to the front and update OverviewScrim to handle the case
    where there's no view above Overview to draw the scrim beneath.
- ENABLE_OVERVIEW_ACTIONS is always false in 2-button mode, since the
  shelf is crucial to that mode.

Bug: 143361609
Change-Id: I743481bb239dc77f7024dc98ba68a43534da2637
parent 8a054061
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
@@ -224,7 +225,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
        if (mode == NO_BUTTON) {
            list.add(new NoButtonQuickSwitchTouchController(this));
            list.add(new NavBarToHomeTouchController(this));
            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
                list.add(new NoButtonNavbarToOverviewTouchController(this));
            } else {
                list.add(new FlingAndHoldTouchController(this));
            }
        } else {
            if (getDeviceProfile().isVerticalBarLayout()) {
                list.add(new OverviewToAllAppsTouchController(this));
+20 −6
Original line number Diff line number Diff line
@@ -30,11 +30,13 @@ import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;

import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;

import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
@@ -44,7 +46,6 @@ import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -115,6 +116,15 @@ public class OverviewState extends LauncherState {
        return new ScaleAndTranslation(1f, 0f, 0f);
    }

    @Override
    public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
        if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()) {
            // Treat the QSB as part of the hotseat so they move together.
            return getHotseatScaleAndTranslation(launcher);
        }
        return super.getQsbScaleAndTranslation(launcher);
    }

    @Override
    public void onStateEnabled(Launcher launcher) {
        AbstractFloatingView.closeAllOpenViews(launcher);
@@ -141,7 +151,7 @@ public class OverviewState extends LauncherState {
        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
            return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
        } else {
            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
            if (ENABLE_OVERVIEW_ACTIONS.get()) {
                return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
            }

@@ -195,9 +205,10 @@ public class OverviewState extends LauncherState {
    @Override
    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
            AnimatorSetBuilder builder) {
        if (fromState == NORMAL && this == OVERVIEW) {
        if ((fromState == NORMAL || fromState == HINT_STATE) && this == OVERVIEW) {
            if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
                builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
                builder.setInterpolator(ANIM_WORKSPACE_SCALE,
                        fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
                builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
            } else {
                builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
@@ -210,8 +221,11 @@ public class OverviewState extends LauncherState {
            }
            builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
            builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
            Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
                    ? OVERSHOOT_1_2
                    : OVERSHOOT_1_7;
            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
            builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
        }
    }
+46 −39
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
    private static final long PEEK_OUT_ANIM_DURATION = 100;
    private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;

    private final MotionPauseDetector mMotionPauseDetector;
    protected final MotionPauseDetector mMotionPauseDetector;
    private final float mMotionPauseMinDisplacement;
    private final float mMotionPauseMaxDisplacement;

@@ -85,7 +85,11 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
        super.onDragStart(start);

        if (handlingOverviewAnim()) {
            mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
            mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
        }
    }

    protected void onMotionPauseChanged(boolean isPaused) {
        RecentsView recentsView = mLauncher.getOverviewPanel();
        recentsView.setOverviewStateEnabled(isPaused);
        if (mPeekAnim != null) {
@@ -107,15 +111,13 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {

        mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
                peekDuration, 0);
            });
        }
    }

    /**
     * @return Whether we are handling the overview animation, rather than
     * having it as part of the existing animation to the target state.
     */
    private boolean handlingOverviewAnim() {
    protected boolean handlingOverviewAnim() {
        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
        return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
    }
@@ -162,7 +164,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
    @Override
    public boolean onDrag(float displacement, MotionEvent event) {
        float upDisplacement = -displacement;
        mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement
        mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
                || upDisplacement < mMotionPauseMinDisplacement
                || upDisplacement > mMotionPauseMaxDisplacement);
        mMotionPauseDetector.addPosition(displacement, event.getEventTime());
        return super.onDrag(displacement, event);
@@ -171,6 +174,19 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
    @Override
    public void onDragEnd(float velocity) {
        if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
            goToOverviewOnDragEnd(velocity);
        } else {
            super.onDragEnd(velocity);
        }

        View searchView = mLauncher.getAppsView().getSearchView();
        if (searchView instanceof FeedbackHandler) {
            ((FeedbackHandler) searchView).resetFeedback();
        }
        mMotionPauseDetector.clear();
    }

    protected void goToOverviewOnDragEnd(float velocity) {
        if (mPeekAnim != null) {
            mPeekAnim.cancel();
        }
@@ -184,15 +200,6 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
            }
        });
        overviewAnim.start();
        } else {
            super.onDragEnd(velocity);
        }

        View searchView = mLauncher.getAppsView().getSearchView();
        if (searchView instanceof FeedbackHandler) {
            ((FeedbackHandler) searchView).resetFeedback();
        }
        mMotionPauseDetector.clear();
    }

    @Override
+174 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.launcher3.uioverrides.touchcontrollers;

import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.MotionEvent;

import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.util.StaggeredWorkspaceAnim;

/**
 * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
 * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
 * first home screen instead of to Overview.
 */
public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchController {

    private boolean mDidTouchStartInNavBar;
    private boolean mReachedOverview;

    public NoButtonNavbarToOverviewTouchController(Launcher l) {
        super(l);
    }

    @Override
    protected boolean canInterceptTouch(MotionEvent ev) {
        mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
        return super.canInterceptTouch(ev);
    }

    @Override
    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
        if (fromState == NORMAL && mDidTouchStartInNavBar) {
            return HINT_STATE;
        } else if (fromState == OVERVIEW && isDragTowardPositive) {
            // Don't allow swiping up to all apps.
            return OVERVIEW;
        }
        return super.getTargetState(fromState, isDragTowardPositive);
    }

    @Override
    protected float initCurrentAnimation(int animComponents) {
        float progressMultiplier = super.initCurrentAnimation(animComponents);
        if (mToState == HINT_STATE) {
            // Track the drag across the entire height of the screen.
            progressMultiplier = -1 / getShiftRange();
        }
        return progressMultiplier;
    }

    @Override
    public void onDragStart(boolean start) {
        super.onDragStart(start);

        mReachedOverview = false;
    }

    @Override
    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
            LauncherState targetState, float velocity, boolean isFling) {
        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
                isFling);
        if (targetState == HINT_STATE) {
            // Normally we compute the duration based on the velocity and distance to the given
            // state, but since the hint state tracks the entire screen without a clear endpoint, we
            // need to manually set the duration to a reasonable value.
            animator.setDuration(HINT_STATE.transitionDuration);
        }
    }

    @Override
    protected void onMotionPauseChanged(boolean isPaused) {
        if (mCurrentAnimation == null) {
            return;
        }
        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
            mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
                mReachedOverview = true;
                maybeSwipeInteractionToOverviewComplete();
            });
        });
        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
    }

    private void maybeSwipeInteractionToOverviewComplete() {
        if (mReachedOverview && mDetector.isSettlingState()) {
            onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
        }
    }

    @Override
    protected boolean handlingOverviewAnim() {
        return mDidTouchStartInNavBar && super.handlingOverviewAnim();
    }

    @Override
    public boolean onDrag(float displacement, MotionEvent event) {
        if (mMotionPauseDetector.isPaused()) {
            // Stay in Overview.
            return true;
        }
        return super.onDrag(displacement, event);
    }

    @Override
    protected void goToOverviewOnDragEnd(float velocity) {
        float velocityDp = dpiFromPx(velocity);
        boolean isFling = Math.abs(velocityDp) > 1;
        LauncherStateManager stateManager = mLauncher.getStateManager();
        if (isFling) {
            // When flinging, go back to home instead of overview.
            if (velocity > 0) {
                stateManager.goToState(NORMAL, true,
                        () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
            } else {
                StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
                        mLauncher, velocity, false /* animateOverviewScrim */);
                staggeredWorkspaceAnim.start();

                // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
                stateManager.cancelAnimation();
                AnimatorSetBuilder builder = new AnimatorSetBuilder();
                long duration = OVERVIEW.transitionDuration;
                AnimatorSet anim = stateManager.createAtomicAnimation(
                        stateManager.getState(), NORMAL, builder,
                        ATOMIC_OVERVIEW_PEEK_COMPONENT, duration);
                anim.addListener(new AnimationSuccessListener() {
                    @Override
                    public void onAnimationSuccess(Animator animator) {
                        onSwipeInteractionCompleted(NORMAL, Touch.SWIPE);
                    }
                });
                anim.start();
            }
        } else {
            maybeSwipeInteractionToOverviewComplete();
        }
    }

    private float dpiFromPx(float pixels) {
        return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
@@ -181,6 +182,12 @@ public class NoButtonQuickSwitchTouchController implements TouchController,

    @Override
    public void onMotionPauseChanged(boolean isPaused) {
        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);

        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
            return;
        }

        ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
        if (shelfState == PEEK) {
            // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
@@ -197,7 +204,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
        }
        mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
                ShelfPeekAnim.DURATION);
        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
    }

    private void setupAnimators() {
Loading