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.touchcontrollers; 17 18 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; 19 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; 20 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; 21 import static com.android.launcher3.LauncherState.ALL_APPS; 22 import static com.android.launcher3.LauncherState.NORMAL; 23 import static com.android.launcher3.LauncherState.OVERVIEW; 24 import static com.android.launcher3.anim.Interpolators.ACCEL; 25 import static com.android.launcher3.anim.Interpolators.DEACCEL; 26 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 27 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE; 28 29 import android.view.MotionEvent; 30 31 import com.android.launcher3.DeviceProfile; 32 import com.android.launcher3.Launcher; 33 import com.android.launcher3.LauncherState; 34 import com.android.launcher3.allapps.AllAppsTransitionController; 35 import com.android.launcher3.anim.Interpolators; 36 import com.android.launcher3.states.StateAnimationConfig; 37 import com.android.launcher3.touch.AbstractStateChangeTouchController; 38 import com.android.launcher3.touch.SingleAxisSwipeDetector; 39 import com.android.launcher3.uioverrides.states.OverviewState; 40 import com.android.quickstep.SystemUiProxy; 41 import com.android.quickstep.util.LayoutUtils; 42 import com.android.quickstep.views.RecentsView; 43 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 44 45 /** 46 * Touch controller for handling various state transitions in portrait UI. 47 */ 48 public class PortraitStatesTouchController extends AbstractStateChangeTouchController { 49 50 private static final String TAG = "PortraitStatesTouchCtrl"; 51 52 /** 53 * The progress at which all apps content will be fully visible. 54 */ 55 public static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f; 56 57 /** 58 * Minimum clamping progress for fading in all apps content 59 */ 60 public static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f; 61 62 /** 63 * Minimum clamping progress for fading in all apps scrim 64 */ 65 public static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f; 66 67 /** 68 * Maximum clamping progress for opaque all apps scrim 69 */ 70 public static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f; 71 72 private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper; 73 PortraitStatesTouchController(Launcher l)74 public PortraitStatesTouchController(Launcher l) { 75 super(l, SingleAxisSwipeDetector.VERTICAL); 76 mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l); 77 } 78 79 @Override canInterceptTouch(MotionEvent ev)80 protected boolean canInterceptTouch(MotionEvent ev) { 81 // If we are swiping to all apps instead of overview, allow it from anywhere. 82 boolean interceptAnywhere = mLauncher.isInState(NORMAL); 83 if (mCurrentAnimation != null) { 84 AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); 85 if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress() 86 || interceptAnywhere) { 87 // If we are already animating from a previous state, we can intercept as long as 88 // the touch is below the current all apps progress (to allow for double swipe). 89 return true; 90 } 91 // Otherwise, don't intercept so they can scroll recents, dismiss a task, etc. 92 return false; 93 } 94 if (mLauncher.isInState(ALL_APPS)) { 95 // In all-apps only listen if the container cannot scroll itself 96 if (!mLauncher.getAppsView().shouldContainerScroll(ev)) { 97 return false; 98 } 99 } else if (mLauncher.isInState(OVERVIEW)) { 100 if (!mOverviewPortraitStateTouchHelper.canInterceptTouch(ev)) { 101 return false; 102 } 103 } else { 104 // For non-normal states, only listen if the event originated below the hotseat height 105 if (!interceptAnywhere && !isTouchOverHotseat(mLauncher, ev)) { 106 return false; 107 } 108 } 109 if (getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE | TYPE_ALL_APPS_EDU) != null) { 110 return false; 111 } 112 return true; 113 } 114 115 @Override getTargetState(LauncherState fromState, boolean isDragTowardPositive)116 protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) { 117 if (fromState == ALL_APPS && !isDragTowardPositive) { 118 return NORMAL; 119 } else if (fromState == OVERVIEW) { 120 return isDragTowardPositive ? OVERVIEW : NORMAL; 121 } else if (fromState == NORMAL && isDragTowardPositive) { 122 return ALL_APPS; 123 } 124 return fromState; 125 } 126 getNormalToAllAppsAnimation()127 private StateAnimationConfig getNormalToAllAppsAnimation() { 128 StateAnimationConfig builder = new StateAnimationConfig(); 129 builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL, 130 ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD, 131 ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD)); 132 builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(ACCEL, 133 ALL_APPS_SCRIM_VISIBLE_THRESHOLD, 134 ALL_APPS_SCRIM_OPAQUE_THRESHOLD)); 135 return builder; 136 } 137 getAllAppsToNormalAnimation()138 private StateAnimationConfig getAllAppsToNormalAnimation() { 139 StateAnimationConfig builder = new StateAnimationConfig(); 140 builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL, 141 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD, 142 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD)); 143 builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL, 144 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD, 145 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); 146 return builder; 147 } 148 149 @Override getConfigForStates( LauncherState fromState, LauncherState toState)150 protected StateAnimationConfig getConfigForStates( 151 LauncherState fromState, LauncherState toState) { 152 final StateAnimationConfig config; 153 if (fromState == NORMAL && toState == ALL_APPS) { 154 config = getNormalToAllAppsAnimation(); 155 } else if (fromState == ALL_APPS && toState == NORMAL) { 156 config = getAllAppsToNormalAnimation(); 157 } else { 158 config = new StateAnimationConfig(); 159 } 160 return config; 161 } 162 163 @Override initCurrentAnimation()164 protected float initCurrentAnimation() { 165 float range = getShiftRange(); 166 long maxAccuracy = (long) (2 * range); 167 168 float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range; 169 float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range; 170 171 float totalShift = endVerticalShift - startVerticalShift; 172 173 final StateAnimationConfig config = totalShift == 0 ? new StateAnimationConfig() 174 : getConfigForStates(mFromState, mToState); 175 config.duration = maxAccuracy; 176 177 if (mCurrentAnimation != null) { 178 mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener); 179 mCurrentAnimation.dispatchOnCancel(); 180 } 181 182 mGoingBetweenStates = true; 183 if (mFromState == OVERVIEW && mToState == NORMAL 184 && mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) { 185 // Reset the state manager, when changing the interaction mode 186 mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */); 187 mGoingBetweenStates = false; 188 mCurrentAnimation = mOverviewPortraitStateTouchHelper 189 .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR) 190 .createPlaybackController(); 191 mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation); 192 RecentsView recentsView = mLauncher.getOverviewPanel(); 193 totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher, 194 mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler()); 195 } else { 196 mCurrentAnimation = mLauncher.getStateManager() 197 .createAnimationToNewWorkspace(mToState, config); 198 } 199 mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener); 200 201 if (totalShift == 0) { 202 totalShift = Math.signum(mFromState.ordinal - mToState.ordinal) 203 * OverviewState.getDefaultSwipeHeight(mLauncher); 204 } 205 return 1 / totalShift; 206 } 207 208 @Override onSwipeInteractionCompleted(LauncherState targetState)209 protected void onSwipeInteractionCompleted(LauncherState targetState) { 210 super.onSwipeInteractionCompleted(targetState); 211 if (mStartState == NORMAL && targetState == OVERVIEW) { 212 SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); 213 } 214 } 215 216 /** 217 * Whether the motion event is over the hotseat. 218 * 219 * @param launcher the launcher activity 220 * @param ev the event to check 221 * @return true if the event is over the hotseat 222 */ isTouchOverHotseat(Launcher launcher, MotionEvent ev)223 static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) { 224 return (ev.getY() >= getHotseatTop(launcher)); 225 } 226 getHotseatTop(Launcher launcher)227 public static int getHotseatTop(Launcher launcher) { 228 DeviceProfile dp = launcher.getDeviceProfile(); 229 int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom; 230 return launcher.getDragLayer().getHeight() - hotseatHeight; 231 } 232 233 @Override onControllerInterceptTouchEvent(MotionEvent ev)234 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 235 switch (ev.getAction()) { 236 case MotionEvent.ACTION_DOWN: 237 InteractionJankMonitorWrapper.begin( 238 mLauncher.getRootView(), InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS); 239 break; 240 241 case MotionEvent.ACTION_CANCEL: 242 case MotionEvent.ACTION_UP: 243 InteractionJankMonitorWrapper.cancel( 244 InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS); 245 break; 246 } 247 return super.onControllerInterceptTouchEvent(ev); 248 249 } 250 251 @Override onReinitToState(LauncherState newToState)252 protected void onReinitToState(LauncherState newToState) { 253 super.onReinitToState(newToState); 254 if (newToState != ALL_APPS) { 255 InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS); 256 } 257 } 258 259 @Override onReachedFinalState(LauncherState toState)260 protected void onReachedFinalState(LauncherState toState) { 261 super.onReinitToState(toState); 262 if (toState == ALL_APPS) { 263 InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS); 264 } 265 } 266 267 @Override clearState()268 protected void clearState() { 269 super.clearState(); 270 InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS); 271 } 272 } 273