1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.uioverrides; 17 18 import static com.android.launcher3.LauncherState.ALL_APPS; 19 import static com.android.launcher3.LauncherState.NORMAL; 20 import static com.android.launcher3.LauncherState.OVERVIEW; 21 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; 22 import static com.android.launcher3.anim.Interpolators.LINEAR; 23 24 import android.animation.TimeInterpolator; 25 import android.animation.ValueAnimator; 26 import android.view.MotionEvent; 27 import android.view.animation.Interpolator; 28 import android.view.animation.OvershootInterpolator; 29 30 import com.android.launcher3.AbstractFloatingView; 31 import com.android.launcher3.DeviceProfile; 32 import com.android.launcher3.Launcher; 33 import com.android.launcher3.LauncherState; 34 import com.android.launcher3.LauncherStateManager.AnimationComponents; 35 import com.android.launcher3.anim.AnimatorPlaybackController; 36 import com.android.launcher3.anim.AnimatorSetBuilder; 37 import com.android.launcher3.anim.Interpolators; 38 import com.android.launcher3.touch.AbstractStateChangeTouchController; 39 import com.android.launcher3.touch.SwipeDetector; 40 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; 41 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 42 import com.android.quickstep.RecentsModel; 43 import com.android.quickstep.TouchInteractionService; 44 import com.android.quickstep.views.RecentsView; 45 import com.android.quickstep.views.TaskView; 46 47 /** 48 * Touch controller for handling various state transitions in portrait UI. 49 */ 50 public class PortraitStatesTouchController extends AbstractStateChangeTouchController { 51 52 private static final String TAG = "PortraitStatesTouchCtrl"; 53 54 private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper(); 55 56 // If true, we will finish the current animation instantly on second touch. 57 private boolean mFinishFastOnSecondTouch; 58 59 PortraitStatesTouchController(Launcher l)60 public PortraitStatesTouchController(Launcher l) { 61 super(l, SwipeDetector.VERTICAL); 62 } 63 64 @Override canInterceptTouch(MotionEvent ev)65 protected boolean canInterceptTouch(MotionEvent ev) { 66 if (mCurrentAnimation != null) { 67 if (mFinishFastOnSecondTouch) { 68 // TODO: Animate to finish instead. 69 mCurrentAnimation.getAnimationPlayer().end(); 70 } 71 72 // If we are already animating from a previous state, we can intercept. 73 return true; 74 } 75 if (mLauncher.isInState(ALL_APPS)) { 76 // In all-apps only listen if the container cannot scroll itself 77 if (!mLauncher.getAppsView().shouldContainerScroll(ev)) { 78 return false; 79 } 80 } else { 81 // For all other states, only listen if the event originated below the hotseat height 82 DeviceProfile dp = mLauncher.getDeviceProfile(); 83 int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom; 84 if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) { 85 return false; 86 } 87 } 88 if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { 89 return false; 90 } 91 return true; 92 } 93 94 @Override getTargetState(LauncherState fromState, boolean isDragTowardPositive)95 protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) { 96 if (fromState == ALL_APPS && !isDragTowardPositive) { 97 // Should swipe down go to OVERVIEW instead? 98 return TouchInteractionService.isConnected() ? 99 mLauncher.getStateManager().getLastState() : NORMAL; 100 } else if (fromState == OVERVIEW) { 101 return isDragTowardPositive ? ALL_APPS : NORMAL; 102 } else if (fromState == NORMAL && isDragTowardPositive) { 103 return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS; 104 } 105 return fromState; 106 } 107 108 @Override getLogContainerTypeForNormalState()109 protected int getLogContainerTypeForNormalState() { 110 return ContainerType.HOTSEAT; 111 } 112 getNormalToOverviewAnimation()113 private AnimatorSetBuilder getNormalToOverviewAnimation() { 114 mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR; 115 116 AnimatorSetBuilder builder = new AnimatorSetBuilder(); 117 builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper); 118 119 return builder; 120 } 121 122 @Override initCurrentAnimation(@nimationComponents int animComponents)123 protected float initCurrentAnimation(@AnimationComponents int animComponents) { 124 float range = getShiftRange(); 125 long maxAccuracy = (long) (2 * range); 126 127 float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range; 128 float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range; 129 130 float totalShift = endVerticalShift - startVerticalShift; 131 132 final AnimatorSetBuilder builder; 133 134 if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) { 135 builder = getNormalToOverviewAnimation(); 136 } else { 137 builder = new AnimatorSetBuilder(); 138 } 139 140 cancelPendingAnim(); 141 142 RecentsView recentsView = mLauncher.getOverviewPanel(); 143 TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage()); 144 if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL 145 && taskView != null) { 146 mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy); 147 mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN); 148 149 Runnable onCancelRunnable = () -> { 150 cancelPendingAnim(); 151 clearState(); 152 }; 153 mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy, 154 onCancelRunnable); 155 mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation); 156 } else { 157 mCurrentAnimation = mLauncher.getStateManager() 158 .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState, 159 animComponents); 160 } 161 162 if (totalShift == 0) { 163 totalShift = Math.signum(mFromState.ordinal - mToState.ordinal) 164 * OverviewState.getDefaultSwipeHeight(mLauncher); 165 } 166 return 1 / totalShift; 167 } 168 cancelPendingAnim()169 private void cancelPendingAnim() { 170 if (mPendingAnimation != null) { 171 mPendingAnimation.finish(false, Touch.SWIPE); 172 mPendingAnimation = null; 173 } 174 } 175 176 @Override updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling)177 protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, 178 LauncherState targetState, float velocity, boolean isFling) { 179 super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, 180 velocity, isFling); 181 handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling); 182 } 183 handleFirstSwipeToOverview(final ValueAnimator animator, final long expectedDuration, final LauncherState targetState, final float velocity, final boolean isFling)184 private void handleFirstSwipeToOverview(final ValueAnimator animator, 185 final long expectedDuration, final LauncherState targetState, final float velocity, 186 final boolean isFling) { 187 if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) { 188 mFinishFastOnSecondTouch = true; 189 if (isFling && expectedDuration != 0) { 190 // Update all apps interpolator to add a bit of overshoot starting from currFraction 191 final float currFraction = mCurrentAnimation.getProgressFraction(); 192 mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress( 193 new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)), currFraction, 1); 194 animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION)) 195 .setInterpolator(LINEAR); 196 } 197 } else { 198 mFinishFastOnSecondTouch = false; 199 } 200 } 201 202 @Override onSwipeInteractionCompleted(LauncherState targetState, int logAction)203 protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { 204 super.onSwipeInteractionCompleted(targetState, logAction); 205 if (mStartState == NORMAL && targetState == OVERVIEW) { 206 RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG); 207 } 208 } 209 210 private static class InterpolatorWrapper implements Interpolator { 211 212 public TimeInterpolator baseInterpolator = LINEAR; 213 214 @Override getInterpolation(float v)215 public float getInterpolation(float v) { 216 return baseInterpolator.getInterpolation(v); 217 } 218 } 219 } 220