| /* |
| * Copyright (C) 2019 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.NORMAL; |
| import static com.android.launcher3.LauncherState.OVERVIEW; |
| import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; |
| import static com.android.launcher3.LauncherStateManager.ANIM_ALL; |
| import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; |
| import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; |
| import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.AnimatorSet; |
| import android.view.HapticFeedbackConstants; |
| import android.view.MotionEvent; |
| import android.view.ViewConfiguration; |
| |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.LauncherState; |
| import com.android.launcher3.anim.AnimatorSetBuilder; |
| import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; |
| import com.android.quickstep.OverviewInteractionState; |
| import com.android.quickstep.util.MotionPauseDetector; |
| import com.android.quickstep.views.RecentsView; |
| import com.android.systemui.shared.system.QuickStepContract; |
| |
| /** |
| * Touch controller which handles swipe and hold to go to Overview |
| */ |
| public class FlingAndHoldTouchController extends PortraitStatesTouchController { |
| |
| private static final long PEEK_ANIM_DURATION = 100; |
| private static final float MAX_DISPLACEMENT_PERCENT = 0.75f; |
| |
| private final MotionPauseDetector mMotionPauseDetector; |
| private final float mMotionPauseMinDisplacement; |
| private final float mMotionPauseMaxDisplacement; |
| |
| private AnimatorSet mPeekAnim; |
| |
| public FlingAndHoldTouchController(Launcher l) { |
| super(l, false /* allowDragToOverview */); |
| mMotionPauseDetector = new MotionPauseDetector(l); |
| mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop(); |
| mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT; |
| } |
| |
| @Override |
| protected long getAtomicDuration() { |
| return 300; |
| } |
| |
| @Override |
| public void onDragStart(boolean start) { |
| mMotionPauseDetector.clear(); |
| |
| 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; |
| mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState, |
| new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, |
| PEEK_ANIM_DURATION); |
| mPeekAnim.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mPeekAnim = null; |
| } |
| }); |
| mPeekAnim.start(); |
| recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, |
| HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); |
| }); |
| } |
| } |
| |
| /** |
| * @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() { |
| int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags(); |
| return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0; |
| } |
| |
| @Override |
| public boolean onDrag(float displacement, MotionEvent event) { |
| float upDisplacement = -displacement; |
| mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement |
| || upDisplacement > mMotionPauseMaxDisplacement); |
| mMotionPauseDetector.addPosition(displacement, event.getEventTime()); |
| return super.onDrag(displacement, event); |
| } |
| |
| @Override |
| public void onDragEnd(float velocity, boolean fling) { |
| if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) { |
| if (mPeekAnim != null) { |
| mPeekAnim.cancel(); |
| } |
| |
| AnimatorSetBuilder builder = new AnimatorSetBuilder(); |
| builder.setInterpolator(AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); |
| builder.setInterpolator(AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE, OVERSHOOT_1_2); |
| AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation( |
| NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION); |
| overviewAnim.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE); |
| } |
| }); |
| overviewAnim.start(); |
| } else { |
| super.onDragEnd(velocity, fling); |
| } |
| mMotionPauseDetector.clear(); |
| } |
| |
| @Override |
| protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) { |
| if (handlingOverviewAnim()) { |
| // We don't want the state transition to all apps to animate overview, |
| // as that will cause a jump after our atomic animation. |
| builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW); |
| } |
| } |
| } |