1 /* 2 * Copyright (C) 2019 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.quickstep; 17 18 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 19 import static com.android.launcher3.LauncherState.NORMAL; 20 import static com.android.launcher3.LauncherState.OVERVIEW; 21 import static com.android.launcher3.LauncherState.QUICK_SWITCH; 22 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 23 import static com.android.launcher3.anim.Interpolators.LINEAR; 24 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 25 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 26 27 import android.animation.Animator; 28 import android.animation.AnimatorSet; 29 import android.content.Context; 30 import android.graphics.Rect; 31 import android.view.MotionEvent; 32 33 import androidx.annotation.Nullable; 34 import androidx.annotation.UiThread; 35 36 import com.android.launcher3.BaseQuickstepLauncher; 37 import com.android.launcher3.DeviceProfile; 38 import com.android.launcher3.Launcher; 39 import com.android.launcher3.LauncherInitListener; 40 import com.android.launcher3.LauncherState; 41 import com.android.launcher3.anim.PendingAnimation; 42 import com.android.launcher3.statehandlers.DepthController; 43 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; 44 import com.android.launcher3.statemanager.StateManager; 45 import com.android.launcher3.taskbar.LauncherTaskbarUIController; 46 import com.android.launcher3.touch.PagedOrientationHandler; 47 import com.android.quickstep.GestureState.GestureEndTarget; 48 import com.android.quickstep.SysUINavigationMode.Mode; 49 import com.android.quickstep.util.ActivityInitListener; 50 import com.android.quickstep.util.AnimatorControllerWithResistance; 51 import com.android.quickstep.util.LayoutUtils; 52 import com.android.quickstep.views.RecentsView; 53 import com.android.systemui.plugins.shared.LauncherOverlayManager; 54 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 55 56 import java.util.function.Consumer; 57 import java.util.function.Predicate; 58 59 /** 60 * {@link BaseActivityInterface} for the in-launcher recents. 61 */ 62 public final class LauncherActivityInterface extends 63 BaseActivityInterface<LauncherState, BaseQuickstepLauncher> { 64 65 public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface(); 66 LauncherActivityInterface()67 private LauncherActivityInterface() { 68 super(true, OVERVIEW, BACKGROUND_APP); 69 } 70 71 @Override getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect, PagedOrientationHandler orientationHandler)72 public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect, 73 PagedOrientationHandler orientationHandler) { 74 calculateTaskSize(context, dp, outRect); 75 if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) { 76 return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right); 77 } else { 78 return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler); 79 } 80 } 81 82 @Override onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState)83 public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) { 84 Launcher launcher = getCreatedActivity(); 85 if (launcher == null) { 86 return; 87 } 88 // When going to home, the state animator we use has SKIP_OVERVIEW because we assume that 89 // setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus, at the end 90 // of the animation, we should ensure recents is at the correct position for NORMAL state. 91 // For example, when doing a long swipe to home, RecentsView may be scaled down. This is 92 // relatively expensive, so do it on the next frame instead of critical path. 93 MAIN_EXECUTOR.getHandler().post(launcher.getStateManager()::reapplyState); 94 95 launcher.getRootView().setForceHideBackArrow(false); 96 notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); 97 } 98 99 @Override onAssistantVisibilityChanged(float visibility)100 public void onAssistantVisibilityChanged(float visibility) { 101 Launcher launcher = getCreatedActivity(); 102 if (launcher == null) { 103 return; 104 } 105 launcher.onAssistantVisibilityChanged(visibility); 106 } 107 108 @Override onOneHandedModeStateChanged(boolean activated)109 public void onOneHandedModeStateChanged(boolean activated) { 110 Launcher launcher = getCreatedActivity(); 111 if (launcher == null) { 112 return; 113 } 114 launcher.onOneHandedStateChanged(activated); 115 } 116 117 @Override prepareRecentsUI(RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback)118 public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, 119 boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) { 120 notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); 121 DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) { 122 @Override 123 protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity, 124 PendingAnimation pa) { 125 super.createBackgroundToOverviewAnim(activity, pa); 126 127 // Animate the blur and wallpaper zoom 128 float fromDepthRatio = BACKGROUND_APP.getDepth(activity); 129 float toDepthRatio = OVERVIEW.getDepth(activity); 130 pa.addFloat(getDepthController(), 131 new ClampedDepthProperty(fromDepthRatio, toDepthRatio), 132 fromDepthRatio, toDepthRatio, LINEAR); 133 } 134 }; 135 136 BaseQuickstepLauncher launcher = factory.initUI(); 137 // Since all apps is not visible, we can safely reset the scroll position. 138 // This ensures then the next swipe up to all-apps starts from scroll 0. 139 launcher.getAppsView().reset(false /* animate */); 140 return factory; 141 } 142 143 @Override createActivityInitListener(Predicate<Boolean> onInitListener)144 public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) { 145 return new LauncherInitListener((activity, alreadyOnHome) -> 146 onInitListener.test(alreadyOnHome)); 147 } 148 149 @Override setOnDeferredActivityLaunchCallback(Runnable r)150 public void setOnDeferredActivityLaunchCallback(Runnable r) { 151 Launcher launcher = getCreatedActivity(); 152 if (launcher == null) { 153 return; 154 } 155 launcher.setOnDeferredActivityLaunchCallback(r); 156 } 157 158 @Nullable 159 @Override getCreatedActivity()160 public BaseQuickstepLauncher getCreatedActivity() { 161 return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); 162 } 163 164 @Nullable 165 @Override getDepthController()166 public DepthController getDepthController() { 167 BaseQuickstepLauncher launcher = getCreatedActivity(); 168 if (launcher == null) { 169 return null; 170 } 171 return launcher.getDepthController(); 172 } 173 174 @Nullable 175 @Override getTaskbarController()176 public LauncherTaskbarUIController getTaskbarController() { 177 BaseQuickstepLauncher launcher = getCreatedActivity(); 178 if (launcher == null) { 179 return null; 180 } 181 return launcher.getTaskbarUIController(); 182 } 183 184 @Nullable 185 @Override getVisibleRecentsView()186 public RecentsView getVisibleRecentsView() { 187 Launcher launcher = getVisibleLauncher(); 188 RecentsView recentsView = 189 launcher != null && launcher.getStateManager().getState().overviewUi 190 ? launcher.getOverviewPanel() : null; 191 if (recentsView == null || (!launcher.hasBeenResumed() 192 && recentsView.getRunningTaskViewId() == -1)) { 193 // If live tile has ended, return null. 194 return null; 195 } 196 return recentsView; 197 } 198 199 @Nullable 200 @UiThread getVisibleLauncher()201 private Launcher getVisibleLauncher() { 202 Launcher launcher = getCreatedActivity(); 203 return (launcher != null) && launcher.isStarted() 204 && ((ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) 205 || launcher.hasBeenResumed()) ? launcher : null; 206 } 207 208 @Override switchToRecentsIfVisible(Runnable onCompleteCallback)209 public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) { 210 Launcher launcher = getVisibleLauncher(); 211 if (launcher == null) { 212 return false; 213 } 214 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) { 215 RecentsView recentsView = getVisibleRecentsView(); 216 if (recentsView == null) { 217 return false; 218 } 219 } 220 221 closeOverlay(); 222 launcher.getStateManager().goToState(OVERVIEW, 223 launcher.getStateManager().shouldAnimateStateChange(), 224 onCompleteCallback == null ? null : forEndCallback(onCompleteCallback)); 225 return true; 226 } 227 228 229 @Override onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable)230 public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) { 231 final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager(); 232 stateManager.addStateListener( 233 new StateManager.StateListener<LauncherState>() { 234 @Override 235 public void onStateTransitionComplete(LauncherState toState) { 236 // Are we going from Recents to Workspace? 237 if (toState == LauncherState.NORMAL) { 238 exitRunnable.run(); 239 notifyRecentsOfOrientation(deviceState); 240 stateManager.removeStateListener(this); 241 } 242 } 243 }); 244 } 245 notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper)246 private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) { 247 // reset layout on swipe to home 248 RecentsView recentsView = getCreatedActivity().getOverviewPanel(); 249 recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), 250 rotationTouchHelper.getDisplayRotation()); 251 } 252 253 @Override getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target)254 public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) { 255 return homeBounds; 256 } 257 258 @Override allowMinimizeSplitScreen()259 public boolean allowMinimizeSplitScreen() { 260 return true; 261 } 262 263 @Override isInLiveTileMode()264 public boolean isInLiveTileMode() { 265 Launcher launcher = getCreatedActivity(); 266 return launcher != null && launcher.getStateManager().getState() == OVERVIEW && 267 launcher.isStarted(); 268 } 269 270 @Override onLaunchTaskFailed()271 public void onLaunchTaskFailed() { 272 Launcher launcher = getCreatedActivity(); 273 if (launcher == null) { 274 return; 275 } 276 launcher.getStateManager().goToState(OVERVIEW); 277 } 278 279 @Override closeOverlay()280 public void closeOverlay() { 281 Launcher launcher = getCreatedActivity(); 282 if (launcher == null) { 283 return; 284 } 285 LauncherOverlayManager om = launcher.getOverlayManager(); 286 if (!launcher.isStarted() || launcher.isForceInvisible()) { 287 om.hideOverlay(false /* animate */); 288 } else { 289 om.hideOverlay(150); 290 } 291 LauncherTaskbarUIController taskbarController = getTaskbarController(); 292 if (taskbarController != null) { 293 taskbarController.hideEdu(); 294 } 295 } 296 297 @Override getParallelAnimationToLauncher(GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks)298 public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget, 299 long duration, RecentsAnimationCallbacks callbacks) { 300 LauncherTaskbarUIController uiController = getTaskbarController(); 301 Animator superAnimator = super.getParallelAnimationToLauncher( 302 endTarget, duration, callbacks); 303 if (uiController == null || callbacks == null) { 304 return superAnimator; 305 } 306 LauncherState toState = stateFromGestureEndTarget(endTarget); 307 Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration); 308 if (superAnimator == null) { 309 return taskbarAnimator; 310 } else { 311 AnimatorSet animatorSet = new AnimatorSet(); 312 animatorSet.playTogether(superAnimator, taskbarAnimator); 313 return animatorSet; 314 } 315 } 316 317 @Override getOverviewScrimColorForState(BaseQuickstepLauncher launcher, LauncherState state)318 protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher, 319 LauncherState state) { 320 return state.getWorkspaceScrimColor(launcher); 321 } 322 323 @Override deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)324 public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { 325 LauncherTaskbarUIController uiController = getTaskbarController(); 326 if (uiController == null) { 327 return super.deferStartingActivity(deviceState, ev); 328 } 329 return uiController.isEventOverAnyTaskbarItem(ev); 330 } 331 332 @Override shouldCancelCurrentGesture()333 public boolean shouldCancelCurrentGesture() { 334 LauncherTaskbarUIController uiController = getTaskbarController(); 335 if (uiController == null) { 336 return super.shouldCancelCurrentGesture(); 337 } 338 return uiController.isDraggingItem(); 339 } 340 341 @Override stateFromGestureEndTarget(GestureEndTarget endTarget)342 public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) { 343 switch (endTarget) { 344 case RECENTS: 345 return OVERVIEW; 346 case NEW_TASK: 347 case LAST_TASK: 348 return QUICK_SWITCH; 349 case HOME: 350 default: 351 return NORMAL; 352 } 353 } 354 } 355