diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 4b5ba951b3..c359423085 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -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)); - list.add(new FlingAndHoldTouchController(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)); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 73c0c97f08..9c78af9af1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -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); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index d388f495bf..ff1b5f61e0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -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,37 +85,39 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { super.onDragStart(start); if (handlingOverviewAnim()) { - mMotionPauseDetector.setOnMotionPauseListener(isPaused -> { - RecentsView recentsView = mLauncher.getOverviewPanel(); - recentsView.setOverviewStateEnabled(isPaused); - if (mPeekAnim != null) { - mPeekAnim.cancel(); - } - LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK; - LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL; - long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION; - mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState, - new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration); - mPeekAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mPeekAnim = null; - } - }); - mPeekAnim.start(); - VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); - - mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1, - peekDuration, 0); - }); + mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged); } } + protected void onMotionPauseChanged(boolean isPaused) { + RecentsView recentsView = mLauncher.getOverviewPanel(); + recentsView.setOverviewStateEnabled(isPaused); + if (mPeekAnim != null) { + mPeekAnim.cancel(); + } + LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK; + LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL; + long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION; + mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState, + new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration); + mPeekAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mPeekAnim = null; + } + }); + mPeekAnim.start(); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); + + 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,19 +174,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { @Override public void onDragEnd(float velocity) { if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) { - if (mPeekAnim != null) { - mPeekAnim.cancel(); - } - - Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( - INDEX_PAUSE_TO_OVERVIEW_ANIM); - overviewAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE); - } - }); - overviewAnim.start(); + goToOverviewOnDragEnd(velocity); } else { super.onDragEnd(velocity); } @@ -195,6 +186,22 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mMotionPauseDetector.clear(); } + protected void goToOverviewOnDragEnd(float velocity) { + if (mPeekAnim != null) { + mPeekAnim.cancel(); + } + + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); + overviewAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE); + } + }); + overviewAnim.start(); + } + @Override protected void goToTargetState(LauncherState targetState, int logAction) { if (mPeekAnim != null && mPeekAnim.isStarted()) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java new file mode 100644 index 0000000000..2ac2d2de8e --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -0,0 +1,208 @@ +/* + * 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.anim.Interpolators.ACCEL_DEACCEL; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.graphics.PointF; +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; +import com.android.quickstep.views.RecentsView; + +/** + * 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 { + + + // How much of the movement to use for translating overview after swipe and hold. + private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f; + private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80; + private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f; + + private final RecentsView mRecentsView; + + private boolean mDidTouchStartInNavBar; + private boolean mReachedOverview; + // The last recorded displacement before we reached overview. + private PointF mStartDisplacement = new PointF(); + + public NoButtonNavbarToOverviewTouchController(Launcher l) { + super(l); + mRecentsView = l.getOverviewPanel(); + } + + @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 yDisplacement, float xDisplacement, MotionEvent event) { + if (mMotionPauseDetector.isPaused()) { + if (!mReachedOverview) { + mStartDisplacement.set(xDisplacement, yDisplacement); + } else { + mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x) + * OVERVIEW_MOVEMENT_FACTOR); + mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y) + * OVERVIEW_MOVEMENT_FACTOR); + } + // Stay in Overview. + return true; + } + return super.onDrag(yDisplacement, xDisplacement, 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 { + if (mReachedOverview) { + float distanceDp = dpiFromPx(Math.max( + Math.abs(mRecentsView.getTranslationX()), + Math.abs(mRecentsView.getTranslationY()))); + long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS, + distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS); + mRecentsView.animate() + .translationX(0) + .translationY(0) + .setInterpolator(ACCEL_DEACCEL) + .setDuration(duration) + .withEndAction(this::maybeSwipeInteractionToOverviewComplete); + } + } + } + + private float dpiFromPx(float pixels) { + return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics()); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 4e08df9c70..8628db0107 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -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() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java index 41be6834f7..217eca559d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter; import android.view.animation.Interpolator; import com.android.launcher3.Launcher; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.uioverrides.states.OverviewState; /** @@ -48,7 +49,7 @@ public class ShelfPeekAnim { * Animates to the given state, canceling the previous animation if it was still running. */ public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { - if (mShelfState == shelfState) { + if (mShelfState == shelfState || FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) { return; } mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM); diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java index befeee0db9..9817e32ae9 100644 --- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java +++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java @@ -15,6 +15,11 @@ */ package com.android.quickstep; +import static android.content.Context.MODE_PRIVATE; + +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; +import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME; + import android.content.Context; import android.content.pm.PackageManager; import android.os.UserManager; @@ -22,6 +27,7 @@ import android.util.Log; import com.android.launcher3.BuildConfig; import com.android.launcher3.MainProcessInitializer; +import com.android.launcher3.config.FeatureFlags; import com.android.systemui.shared.system.ThreadedRendererCompat; @SuppressWarnings("unused") @@ -50,5 +56,21 @@ public class QuickstepProcessInitializer extends MainProcessInitializer { // Elevate GPU priority for Quickstep and Remote animations. ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG); + + // Force disable some feature flags based on the system ui navigation mode. + SysUINavigationMode.Mode currMode = SysUINavigationMode.INSTANCE.get(context) + .addModeChangeListener(mode -> disableFeatureFlagsForSysuiNavMode(context, mode)); + disableFeatureFlagsForSysuiNavMode(context, currMode); + } + + private void disableFeatureFlagsForSysuiNavMode(Context ctx, SysUINavigationMode.Mode mode) { + if (mode == SysUINavigationMode.Mode.TWO_BUTTONS) { + ctx.getSharedPreferences(FLAGS_PREF_NAME, MODE_PRIVATE) + .edit() + .putBoolean(ENABLE_OVERVIEW_ACTIONS.key, false) + .apply(); + + FeatureFlags.initialize(ctx); + } } } diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 7885f5cb9b..7ed1e21dfb 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -41,6 +41,8 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; import com.android.quickstep.SysUINavigationMode; @@ -155,12 +157,18 @@ public class ShelfScrimView extends ScrimView mRemainingScreenPathValid = false; mShiftRange = mLauncher.getAllAppsController().getShiftRange(); + Context context = getContext(); if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) { - mMidProgress = 1; mDragHandleProgress = 1; - mMidAlpha = 0; + if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) { + // Fade in all apps background quickly to distinguish from swiping from nav bar. + mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha); + mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher); + } else { + mMidAlpha = 0; + mMidProgress = 1; + } } else { - Context context = getContext(); mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha); mMidProgress = OVERVIEW.getVerticalProgress(mLauncher); Rect hotseatPadding = dp.getHotseatLayoutPadding(); diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java index a7c33a9549..3e84a76695 100644 --- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java +++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java @@ -68,7 +68,7 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest { private DigitalWellBeingToast getToast() { executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW)); - waitForState("Launcher internal state didn't switch to Overview", OVERVIEW); + waitForState("Launcher internal state didn't switch to Overview", () -> OVERVIEW); final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher)); return getFromLauncher(launcher -> { diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index e34ea4acd0..c99df10ff1 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -85,7 +85,8 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Ignore // Enable after b/131115533 public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException { mDevice.pressRecentApps(); - waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW); + waitForState("Launcher internal state didn't switch to Overview", + () -> LauncherState.OVERVIEW); assertNotNull("getOverview() returned null", mLauncher.getOverview()); } @@ -96,7 +97,8 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { public void testWorkspaceSwitchToAllApps() { assertNotNull("switchToAllApps() returned null", mLauncher.getWorkspace().switchToAllApps()); - assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); } @Test @@ -115,7 +117,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { // mLauncher.pressHome() also tests an important case of pressing home while in background. Overview overview = mLauncher.pressHome().switchToOverview(); assertTrue("Launcher internal state didn't switch to Overview", - isInState(LauncherState.OVERVIEW)); + isInState(() -> LauncherState.OVERVIEW)); executeOnLauncher( launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3)); @@ -124,14 +126,16 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { 0, getCurrentOverviewPage(launcher))); overview.flingForward(); - assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW)); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); final Integer currentTaskAfterFlingForward = getFromLauncher( launcher -> getCurrentOverviewPage(launcher)); executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", currentTaskAfterFlingForward > 0)); overview.flingBackward(); - assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW)); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing", getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward)); @@ -150,7 +154,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { // Test dismissing a task. overview = mLauncher.pressHome().switchToOverview(); assertTrue("Launcher internal state didn't switch to Overview", - isInState(LauncherState.OVERVIEW)); + isInState(() -> LauncherState.OVERVIEW)); final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher)); task = overview.getCurrentTask(); assertNotNull("overview.getCurrentTask() returned null (2)", task); @@ -165,29 +169,29 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { final AllAppsFromOverview allApps = overview.switchToAllApps(); assertNotNull("overview.switchToAllApps() returned null (1)", allApps); assertTrue("Launcher internal state is not All Apps (1)", - isInState(LauncherState.ALL_APPS)); + isInState(() -> LauncherState.ALL_APPS)); overview = allApps.switchBackToOverview(); assertNotNull("allApps.switchBackToOverview() returned null", overview); assertTrue("Launcher internal state didn't switch to Overview", - isInState(LauncherState.OVERVIEW)); + isInState(() -> LauncherState.OVERVIEW)); // Test UIDevice.pressBack() overview.switchToAllApps(); assertNotNull("overview.switchToAllApps() returned null (2)", allApps); assertTrue("Launcher internal state is not All Apps (2)", - isInState(LauncherState.ALL_APPS)); + isInState(() -> LauncherState.ALL_APPS)); mDevice.pressBack(); mLauncher.getOverview(); } // Test UIDevice.pressHome, once we are in AllApps. mDevice.pressHome(); - waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); // Test dismissing all tasks. mLauncher.getWorkspace().switchToOverview().dismissAllTasks(); - waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); executeOnLauncher( launcher -> assertEquals("Still have tasks after dismissing all", 0, getTaskCount(launcher))); @@ -205,7 +209,8 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { public void testAppIconLaunchFromAllAppsFromOverview() throws Exception { final AllApps allApps = mLauncher.getWorkspace().switchToOverview().switchToAllApps(); - assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); TaplTestsLauncher3.runIconLaunchFromAllAppsTest(this, allApps); } @@ -217,7 +222,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { assertNotNull("Workspace.switchToOverview() returned null", mLauncher.pressHome().switchToOverview()); assertTrue("Launcher internal state didn't switch to Overview", - isInState(LauncherState.OVERVIEW)); + isInState(() -> LauncherState.OVERVIEW)); } @Test @@ -229,7 +234,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { assertNotNull("Background.switchToOverview() returned null", background.switchToOverview()); assertTrue("Launcher internal state didn't switch to Overview", - isInState(LauncherState.OVERVIEW)); + isInState(() -> LauncherState.OVERVIEW)); } private Background getAndAssertBackground() { @@ -252,9 +257,11 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { TaplTestsLauncher3.runAllAppsTest(this, mLauncher.getAllApps()); // Testing pressHome. - assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); assertNotNull("pressHome returned null", mLauncher.pressHome()); - assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); + assertTrue("Launcher internal state is not Home", + isInState(() -> LauncherState.NORMAL)); assertNotNull("getHome returned null", mLauncher.getWorkspace()); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 06f3453981..2ccdd6f4a9 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1180,6 +1180,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mDropTargetBar.setup(mDragController); mAllAppsController.setupViews(mAppsView); + + if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) { + // Overview is above all other launcher elements, including qsb, so move it to the top. + mOverviewPanel.bringToFront(); + } } /** diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index 74362ed5a3..cdfd257d6d 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; + import android.graphics.drawable.Drawable; import android.util.FloatProperty; import android.util.Property; @@ -28,9 +30,10 @@ public class LauncherAnimUtils { * easier access from static classes and enums */ public static final int ALL_APPS_TRANSITION_MS = 320; - public static final int OVERVIEW_TRANSITION_MS = 250; + public static final int OVERVIEW_TRANSITION_MS = ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250; public static final int SPRING_LOADED_TRANSITION_MS = 150; public static final int SPRING_LOADED_EXIT_DELAY = 500; + public static final int HINT_TRANSITION_MS = 80; // The progress of an animation to all apps must be at least this far along to snap to all apps. public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f; diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index d2b447b4b0..8b80cba7ad 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -30,9 +30,11 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; import static com.android.launcher3.anim.Interpolators.clampToProgress; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL; +import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; @@ -42,6 +44,7 @@ import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORD import android.view.animation.Interpolator; import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.states.HintState; import com.android.launcher3.states.SpringLoadedState; import com.android.launcher3.uioverrides.states.AllAppsState; import com.android.launcher3.uioverrides.states.OverviewState; @@ -88,7 +91,7 @@ public class LauncherState { } }; - private static final LauncherState[] sAllStates = new LauncherState[7]; + private static final LauncherState[] sAllStates = new LauncherState[8]; /** * TODO: Create a separate class for NORMAL state. @@ -104,6 +107,7 @@ public class LauncherState { public static final LauncherState SPRING_LOADED = new SpringLoadedState( SPRING_LOADED_STATE_ORDINAL); public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL); + public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL); public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL); public static final LauncherState OVERVIEW_PEEK = @@ -212,6 +216,10 @@ public class LauncherState { return launcher.getOverviewScaleAndTranslationForNormalState(); } + public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) { + return new ScaleAndTranslation(1, 0, 0); + } + public float getOverviewFullscreenProgress() { return 0; } @@ -319,6 +327,10 @@ public class LauncherState { if (!isHotseatVisible) { hotseat.setScaleX(0.92f); hotseat.setScaleY(0.92f); + if (ENABLE_OVERVIEW_ACTIONS.get()) { + launcher.getAppsView().setScaleX(0.92f); + launcher.getAppsView().setScaleY(0.92f); + } } } else if (this == NORMAL && fromState == OVERVIEW_PEEK) { // Keep fully visible until the very end (when overview is offscreen) to make invisible. diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 6c0d95eeda..7f443b60b8 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -381,7 +381,7 @@ public final class Utilities { return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } - public static float dpiFromPx(int size, DisplayMetrics metrics){ + public static float dpiFromPx(float size, DisplayMetrics metrics) { float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT; return (size / densityRatio); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index abbf59dacd..670363efe2 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3255,7 +3255,8 @@ public class Workspace extends PagedView return mOverlayShown; } - void moveToDefaultScreen() { + /** Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. */ + public void moveToDefaultScreen() { int page = DEFAULT_PAGE; if (!workspaceInModalState() && getNextPage() != page) { snapToPage(page); diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 7a7e1fee6c..c33392d53d 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -36,6 +36,7 @@ import android.view.animation.Interpolator; import com.android.launcher3.LauncherState.PageAlphaProvider; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.LauncherStateManager.AnimationConfig; +import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.graphics.WorkspaceAndHotseatScrim; @@ -77,6 +78,7 @@ public class WorkspaceStateTransitionAnimation { ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher); ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation( mLauncher); + ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher); mNewScale = scaleAndTranslation.scale; PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher); final int childCount = mWorkspace.getChildCount(); @@ -90,24 +92,24 @@ public class WorkspaceStateTransitionAnimation { pageAlphaProvider.interpolator); boolean playAtomicComponent = config.playAtomicOverviewScaleComponent(); Hotseat hotseat = mWorkspace.getHotseat(); + // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace. + AllAppsContainerView qsbScaleView = mLauncher.getAppsView(); + View qsbView = qsbScaleView.getSearchView(); if (playAtomicComponent) { Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT); propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator); if (!hotseat.getRotationMode().isTransposed) { - // Set the hotseat's pivot point to match the workspace's, so that it scales - // together. Since both hotseat and workspace can move, transform the point - // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and - // related methods. - hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop()); - hotseat.setPivotX(mWorkspace.getPivotX() - + mWorkspace.getLeft() - hotseat.getLeft()); + setPivotToScaleWithWorkspace(hotseat); + setPivotToScaleWithWorkspace(qsbScaleView); } float hotseatScale = hotseatScaleAndTranslation.scale; Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE, scaleInterpolator); propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, hotseatScaleInterpolator); + propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale, + hotseatScaleInterpolator); float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0; propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator); @@ -134,10 +136,24 @@ public class WorkspaceStateTransitionAnimation { hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator); propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y, hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator); + propertySetter.setFloat(qsbView, View.TRANSLATION_Y, + qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator); setScrim(propertySetter, state); } + /** + * Set the given view's pivot point to match the workspace's, so that it scales together. Since + * both this view and workspace can move, transform the point manually instead of using + * dragLayer.getDescendantCoordRelativeToSelf and related methods. + */ + private void setPivotToScaleWithWorkspace(View sibling) { + sibling.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() + - sibling.getTop() - sibling.getTranslationY()); + sibling.setPivotX(mWorkspace.getPivotX() + mWorkspace.getLeft() + - sibling.getLeft() - sibling.getTranslationX()); + } + public void setScrim(PropertySetter propertySetter, LauncherState state) { WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim(); propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher), diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 4a52795f89..1c277ab8c0 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -26,6 +26,7 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; @@ -250,14 +251,24 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */ + public void dispatchOnCancelWithoutCancelRunnable() { + dispatchOnCancelWithoutCancelRunnable(null); + } + /** * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This * is intended to be used only if you need to cancel but want to defer cleaning up yourself. + * @param callback An optional callback to run after dispatching the cancel but before resetting + * the onCancelRunnable. */ - public void dispatchOnCancelWithoutCancelRunnable() { + public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) { Runnable onCancel = mOnCancelRunnable; setOnCancelRunnable(null); dispatchOnCancel(); + if (callback != null) { + callback.run(); + } setOnCancelRunnable(onCancel); } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 8ccb369138..b1a2c3368d 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -117,7 +117,8 @@ public final class FeatureFlags { "Show launcher preview in grid picker"); public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag( - "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions in Overview"); + "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions instead of the shelf in Overview." + + " As part of this decoupling, also distinguish swipe up from nav bar vs above it."); public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag( "ENABLE_DATABASE_RESTORE", true, diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 8823bde624..92f35e2b19 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -536,6 +536,9 @@ public class DragLayer extends BaseDragLayer { mOverviewScrim.updateCurrentScrimmedView(this); mFocusIndicatorHelper.draw(canvas); super.dispatchDraw(canvas); + if (mOverviewScrim.getScrimmedView() == null) { + mOverviewScrim.draw(canvas); + } } @Override diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java index d707403ed2..94acbfdcca 100644 --- a/src/com/android/launcher3/graphics/OverviewScrim.java +++ b/src/com/android/launcher3/graphics/OverviewScrim.java @@ -55,16 +55,17 @@ public class OverviewScrim extends Scrim { mCurrentScrimmedView = mStableScrimmedView; int currentIndex = root.indexOfChild(mCurrentScrimmedView); final int childCount = root.getChildCount(); - while (mCurrentScrimmedView.getVisibility() != VISIBLE && currentIndex < childCount) { + while (mCurrentScrimmedView != null && mCurrentScrimmedView.getVisibility() != VISIBLE + && currentIndex < childCount) { currentIndex++; mCurrentScrimmedView = root.getChildAt(currentIndex); } } /** - * @return The view to draw the scrim behind. + * @return The view to draw the scrim behind, or null if all visible views should be scrimmed. */ - public View getScrimmedView() { + public @Nullable View getScrimmedView() { return mCurrentScrimmedView; } } diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java new file mode 100644 index 0000000000..cb56097f05 --- /dev/null +++ b/src/com/android/launcher3/states/HintState.java @@ -0,0 +1,54 @@ +/* + * 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.states; + +import static com.android.launcher3.LauncherAnimUtils.HINT_TRANSITION_MS; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.Workspace; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; + +/** + * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen. + */ +public class HintState extends LauncherState { + + private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE + | FLAG_HAS_SYS_UI_SCRIM; + + public HintState(int id) { + super(id, ContainerType.DEFAULT_CONTAINERTYPE, HINT_TRANSITION_MS, STATE_FLAGS); + } + + @Override + public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) { + return new ScaleAndTranslation(0.9f, 0, 0); + } + + @Override + public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) { + // Treat the QSB as part of the hotseat so they move together. + return getHotseatScaleAndTranslation(launcher); + } + + @Override + public void onStateTransitionEnd(Launcher launcher) { + launcher.getStateManager().goToState(NORMAL); + Workspace workspace = launcher.getWorkspace(); + workspace.post(workspace::moveToDefaultScreen); + } +} diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 01c207f907..5c200509d9 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -31,6 +31,7 @@ public final class TestProtocol { public static final int QUICK_SWITCH_STATE_ORDINAL = 4; public static final int ALL_APPS_STATE_ORDINAL = 5; public static final int BACKGROUND_APP_STATE_ORDINAL = 6; + public static final int HINT_STATE_ORDINAL = 7; public static final String TAPL_EVENTS_TAG = "TaplEvents"; public static String stateOrdinalToString(int ordinal) { @@ -49,6 +50,8 @@ public final class TestProtocol { return "AllApps"; case BACKGROUND_APP_STATE_ORDINAL: return "Background"; + case HINT_STATE_ORDINAL: + return "Hint"; default: return null; } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index d193bef40d..3ec480d43e 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -525,7 +525,11 @@ public abstract class AbstractStateChangeTouchController if (targetState != mStartState) { logReachedState(logAction, targetState); } - mLauncher.getStateManager().goToState(targetState, false /* animated */); + if (!mLauncher.isInState(targetState)) { + // If we're already in the target state, don't jump to it at the end of the animation in + // case the user started interacting with it before the animation finished. + mLauncher.getStateManager().goToState(targetState, false /* animated */); + } mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0); } diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java index f2ebc45193..9d406f3bef 100644 --- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java +++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java @@ -54,8 +54,8 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { } @Override - boolean canScrollStart(PointF displacement, float touchSlop) { - return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop); + float extractOrthogonalDirection(PointF direction) { + return direction.x; } }; @@ -80,9 +80,10 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { } @Override - boolean canScrollStart(PointF displacement, float touchSlop) { - return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop); + float extractOrthogonalDirection(PointF direction) { + return direction.y; } + }; private final Direction mDir; @@ -126,7 +127,9 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { @Override protected boolean shouldScrollStart(PointF displacement) { // Reject cases where the angle or slop condition is not met. - if (!mDir.canScrollStart(displacement, mTouchSlop)) { + float minDisplacement = Math.max(mTouchSlop, + Math.abs(mDir.extractOrthogonalDirection(displacement))); + if (Math.abs(mDir.extractDirection(displacement)) < minDisplacement) { return false; } @@ -150,7 +153,8 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { @Override protected void reportDraggingInternal(PointF displacement, MotionEvent event) { - mListener.onDrag(mDir.extractDirection(displacement), event); + mListener.onDrag(mDir.extractDirection(displacement), + mDir.extractOrthogonalDirection(displacement), event); } @Override @@ -164,13 +168,16 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { /** @param start whether this was the original drag start, as opposed to a recatch. */ void onDragStart(boolean start); - // TODO remove boolean onDrag(float displacement); default boolean onDrag(float displacement, MotionEvent event) { return onDrag(displacement); } + default boolean onDrag(float displacement, float orthogonalDisplacement, MotionEvent ev) { + return onDrag(displacement, ev); + } + void onDragEnd(float velocity); } @@ -183,8 +190,7 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { /** Returns the part of the given {@link PointF} that is relevant to this direction. */ abstract float extractDirection(PointF point); - /** Reject cases where the angle or slop condition is not met. */ - abstract boolean canScrollStart(PointF displacement, float touchSlop); + abstract float extractOrthogonalDirection(PointF point); } } diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java index efbd9c99c3..33066e403e 100644 --- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java +++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java @@ -49,7 +49,7 @@ public class PromiseIconUiTest extends AbstractLauncherUiTest { super.setUp(); mDevice.pressHome(); waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null); - waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); mSessionId = -1; } diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java index 6d463b5684..b0ece77c9b 100644 --- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java +++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java @@ -22,15 +22,16 @@ import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyFloat; -import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.util.Log; +import android.view.MotionEvent; import android.view.ViewConfiguration; import androidx.test.InstrumentationRegistry; @@ -158,7 +159,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDrag(anyFloat(), anyObject()); + verify(mMockListener).onDrag(anyFloat(), anyFloat(), any(MotionEvent.class)); } @Test diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 19997eb028..25670e7ee0 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -84,6 +84,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; /** * Base class for all instrumentation tests providing various utility methods. @@ -281,9 +282,9 @@ public abstract class AbstractLauncherUiTest { // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting // the results of that gesture because the wait can hide flakeness. - protected void waitForState(String message, LauncherState state) { + protected void waitForState(String message, Supplier state) { waitForLauncherCondition(message, - launcher -> launcher.getStateManager().getCurrentStableState() == state); + launcher -> launcher.getStateManager().getCurrentStableState() == state.get()); } protected void waitForResumed(String message) { @@ -430,9 +431,9 @@ public abstract class AbstractLauncherUiTest { return !launcher.hasBeenResumed(); } - protected boolean isInState(LauncherState state) { + protected boolean isInState(Supplier state) { if (!TestHelpers.isInLauncherProcess()) return true; - return getFromLauncher(launcher -> launcher.getStateManager().getState() == state); + return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get()); } protected int getAllAppsScroll(Launcher launcher) { diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 9b4023e3d3..4b72882dea 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -76,7 +76,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { test.clearLauncherData(); test.mDevice.pressHome(); test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null); - test.waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + test.waitForState("Launcher internal state didn't switch to Home", + () -> LauncherState.NORMAL); test.waitForResumed("Launcher internal state is still Background"); // Check that we switched to home. test.mLauncher.getWorkspace(); @@ -146,7 +147,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { assertTrue( "Launcher internal state is not All Apps", - test.isInState(LauncherState.ALL_APPS)); + test.isInState(() -> LauncherState.ALL_APPS)); // Test flinging forward and backward. test.executeOnLauncher(launcher -> assertEquals( @@ -155,7 +156,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { allApps.flingForward(); assertTrue("Launcher internal state is not All Apps", - test.isInState(LauncherState.ALL_APPS)); + test.isInState(() -> LauncherState.ALL_APPS)); final Integer flingForwardY = test.getFromLauncher( launcher -> test.getAllAppsScroll(launcher)); test.executeOnLauncher( @@ -165,7 +166,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { allApps.flingBackward(); assertTrue( "Launcher internal state is not All Apps", - test.isInState(LauncherState.ALL_APPS)); + test.isInState(() -> LauncherState.ALL_APPS)); final Integer flingBackwardY = test.getFromLauncher( launcher -> test.getAllAppsScroll(launcher)); test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps", @@ -182,7 +183,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { assertTrue( "Launcher internal state is not All Apps", - test.isInState(LauncherState.ALL_APPS)); + test.isInState(() -> LauncherState.ALL_APPS)); } finally { allApps.unfreeze(); } @@ -193,7 +194,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { public void testWorkspaceSwitchToAllApps() { assertNotNull("switchToAllApps() returned null", mLauncher.getWorkspace().switchToAllApps()); - assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); } @Test @@ -219,7 +221,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { // Test flinging workspace. workspace.flingBackward(); - assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); + assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL)); executeOnLauncher( launcher -> assertEquals("Flinging back didn't switch workspace to page #0", 0, getCurrentWorkspacePage(launcher))); @@ -228,7 +230,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { executeOnLauncher( launcher -> assertEquals("Flinging forward didn't switch workspace to page #1", 1, getCurrentWorkspacePage(launcher))); - assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); + assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL)); // Test starting a workspace app. final AppIcon app = workspace.getWorkspaceAppIcon("Chrome"); @@ -254,7 +256,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @PortraitLandscape public void testAppIconLaunchFromAllAppsFromHome() throws Exception { final AllApps allApps = mLauncher.getWorkspace().switchToAllApps(); - assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); runIconLaunchFromAllAppsTest(this, allApps); }