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.quickstep; 17 18 import static com.android.app.animation.Interpolators.ACCELERATE_2; 19 import static com.android.app.animation.Interpolators.INSTANT; 20 import static com.android.app.animation.Interpolators.LINEAR; 21 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; 22 import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION; 23 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; 24 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM; 25 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM; 26 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; 27 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; 28 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; 29 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; 30 31 import android.animation.Animator; 32 import android.animation.AnimatorListenerAdapter; 33 import android.animation.AnimatorSet; 34 import android.graphics.Color; 35 import android.view.MotionEvent; 36 37 import androidx.annotation.Nullable; 38 import androidx.annotation.UiThread; 39 40 import com.android.launcher3.anim.AnimatorPlaybackController; 41 import com.android.launcher3.anim.PendingAnimation; 42 import com.android.launcher3.statehandlers.DepthController; 43 import com.android.launcher3.statehandlers.DesktopVisibilityController; 44 import com.android.launcher3.statemanager.BaseState; 45 import com.android.launcher3.statemanager.StatefulActivity; 46 import com.android.launcher3.taskbar.TaskbarUIController; 47 import com.android.launcher3.util.DisplayController; 48 import com.android.launcher3.util.NavigationMode; 49 import com.android.quickstep.util.AnimatorControllerWithResistance; 50 import com.android.quickstep.views.RecentsView; 51 import com.android.quickstep.views.RecentsViewContainer; 52 import com.android.systemui.shared.recents.model.ThumbnailData; 53 54 import java.util.HashMap; 55 import java.util.Optional; 56 import java.util.function.Consumer; 57 58 /** 59 * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. 60 */ 61 public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>, 62 ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE> & RecentsViewContainer> extends 63 BaseContainerInterface<STATE_TYPE, ACTIVITY_TYPE> { 64 private final STATE_TYPE mBackgroundState; 65 66 private STATE_TYPE mTargetState; 67 68 @Nullable private Runnable mOnInitBackgroundStateUICallback = null; 69 BaseActivityInterface(boolean rotationSupportedByActivity, STATE_TYPE overviewState, STATE_TYPE backgroundState)70 protected BaseActivityInterface(boolean rotationSupportedByActivity, 71 STATE_TYPE overviewState, STATE_TYPE backgroundState) { 72 this.rotationSupportedByActivity = rotationSupportedByActivity; 73 mTargetState = overviewState; 74 mBackgroundState = backgroundState; 75 } 76 77 /** 78 * Called when the current gesture transition is cancelled. 79 * @param activityVisible Whether the user can see the changes we make here, so try to animate. 80 * @param endTarget If the gesture ended before we got cancelled, where we were headed. 81 */ onTransitionCancelled(boolean activityVisible, @Nullable GestureState.GestureEndTarget endTarget)82 public void onTransitionCancelled(boolean activityVisible, 83 @Nullable GestureState.GestureEndTarget endTarget) { 84 ACTIVITY_TYPE activity = getCreatedContainer(); 85 if (activity == null) { 86 return; 87 } 88 STATE_TYPE startState = activity.getStateManager().getRestState(); 89 if (endTarget != null) { 90 // We were on our way to this state when we got canceled, end there instead. 91 startState = stateFromGestureEndTarget(endTarget); 92 DesktopVisibilityController controller = getDesktopVisibilityController(); 93 if (controller != null && controller.areDesktopTasksVisible() 94 && endTarget == LAST_TASK) { 95 // When we are cancelling the transition and going back to last task, move to 96 // rest state instead when desktop tasks are visible. 97 // If a fullscreen task is visible, launcher goes to normal state when the 98 // activity is stopped. This does not happen when desktop tasks are visible 99 // on top of launcher. Force the launcher state to rest state here. 100 startState = activity.getStateManager().getRestState(); 101 // Do not animate the transition 102 activityVisible = false; 103 } 104 } 105 activity.getStateManager().goToState(startState, activityVisible); 106 } 107 108 @Nullable getCreatedContainer()109 public abstract ACTIVITY_TYPE getCreatedContainer(); 110 111 @Nullable getDepthController()112 public DepthController getDepthController() { 113 return null; 114 } 115 isResumed()116 public final boolean isResumed() { 117 ACTIVITY_TYPE activity = getCreatedContainer(); 118 return activity != null && activity.hasBeenResumed(); 119 } 120 isStarted()121 public final boolean isStarted() { 122 ACTIVITY_TYPE activity = getCreatedContainer(); 123 return activity != null && activity.isStarted(); 124 } 125 126 @UiThread 127 @Nullable getVisibleRecentsView()128 public abstract <T extends RecentsView> T getVisibleRecentsView(); 129 130 @UiThread switchToRecentsIfVisible(Animator.AnimatorListener animatorListener)131 public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener); 132 deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)133 public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { 134 TaskbarUIController controller = getTaskbarController(); 135 boolean isEventOverBubbleBarStashHandle = 136 controller != null && controller.isEventOverBubbleBarStashHandle(ev); 137 return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons() 138 || isTrackpadMultiFingerSwipe(ev) || isEventOverBubbleBarStashHandle; 139 } 140 141 /** 142 * Closes any overlays. 143 */ closeOverlay()144 public void closeOverlay() { 145 Optional.ofNullable(getTaskbarController()).ifPresent( 146 TaskbarUIController::hideOverlayWindow); 147 } 148 switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, Runnable runnable)149 public void switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, 150 Runnable runnable) { 151 ACTIVITY_TYPE activity = getCreatedContainer(); 152 if (activity == null) { 153 return; 154 } 155 RecentsView recentsView = activity.getOverviewPanel(); 156 if (recentsView == null) { 157 if (runnable != null) { 158 runnable.run(); 159 } 160 return; 161 } 162 recentsView.switchToScreenshot(thumbnailDatas, runnable); 163 } 164 165 runOnInitBackgroundStateUI(Runnable callback)166 protected void runOnInitBackgroundStateUI(Runnable callback) { 167 ACTIVITY_TYPE activity = getCreatedContainer(); 168 if (activity != null && activity.getStateManager().getState() == mBackgroundState) { 169 callback.run(); 170 onInitBackgroundStateUI(); 171 return; 172 } 173 mOnInitBackgroundStateUICallback = callback; 174 } 175 onInitBackgroundStateUI()176 private void onInitBackgroundStateUI() { 177 if (mOnInitBackgroundStateUICallback != null) { 178 mOnInitBackgroundStateUICallback.run(); 179 mOnInitBackgroundStateUICallback = null; 180 } 181 } 182 183 public interface AnimationFactory { 184 createActivityInterface(long transitionLength)185 void createActivityInterface(long transitionLength); 186 187 /** 188 * @param attached Whether to show RecentsView alongside the app window. If false, recents 189 * will be hidden by some property we can animate, e.g. alpha. 190 * @param animate Whether to animate recents to/from its new attached state. 191 */ setRecentsAttachedToAppWindow(boolean attached, boolean animate)192 default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { } 193 isRecentsAttachedToAppWindow()194 default boolean isRecentsAttachedToAppWindow() { 195 return false; 196 } 197 hasRecentsEverAttachedToAppWindow()198 default boolean hasRecentsEverAttachedToAppWindow() { 199 return false; 200 } 201 202 /** Called when the gesture ends and we know what state it is going towards */ setEndTarget(GestureState.GestureEndTarget endTarget)203 default void setEndTarget(GestureState.GestureEndTarget endTarget) { } 204 } 205 206 class DefaultAnimationFactory implements AnimationFactory { 207 208 protected final ACTIVITY_TYPE mActivity; 209 private final STATE_TYPE mStartState; 210 private final Consumer<AnimatorControllerWithResistance> mCallback; 211 212 private boolean mIsAttachedToWindow; 213 private boolean mHasEverAttachedToWindow; 214 DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback)215 DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) { 216 mCallback = callback; 217 218 mActivity = getCreatedContainer(); 219 mStartState = mActivity.getStateManager().getState(); 220 } 221 initBackgroundStateUI()222 protected ACTIVITY_TYPE initBackgroundStateUI() { 223 STATE_TYPE resetState = mStartState; 224 if (mStartState.shouldDisableRestore()) { 225 resetState = mActivity.getStateManager().getRestState(); 226 } 227 mActivity.getStateManager().setRestState(resetState); 228 mActivity.getStateManager().goToState(mBackgroundState, false); 229 onInitBackgroundStateUI(); 230 return mActivity; 231 } 232 233 @Override createActivityInterface(long transitionLength)234 public void createActivityInterface(long transitionLength) { 235 PendingAnimation pa = new PendingAnimation(transitionLength * 2); 236 createBackgroundToOverviewAnim(mActivity, pa); 237 AnimatorPlaybackController controller = pa.createPlaybackController(); 238 mActivity.getStateManager().setCurrentUserControlledAnimation(controller); 239 240 // Since we are changing the start position of the UI, reapply the state, at the end 241 controller.setEndAction(() -> mActivity.getStateManager().goToState( 242 controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState, 243 /* animated= */ false)); 244 245 RecentsView recentsView = mActivity.getOverviewPanel(); 246 AnimatorControllerWithResistance controllerWithResistance = 247 AnimatorControllerWithResistance.createForRecents(controller, mActivity, 248 recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(), 249 recentsView, RECENTS_SCALE_PROPERTY, recentsView, 250 TASK_SECONDARY_TRANSLATION); 251 mCallback.accept(controllerWithResistance); 252 253 // Creating the activity controller animation sometimes reapplies the launcher state 254 // (because we set the animation as the current state animation), so we reapply the 255 // attached state here as well to ensure recents is shown/hidden appropriately. 256 if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) { 257 setRecentsAttachedToAppWindow(mIsAttachedToWindow, false); 258 } 259 } 260 261 @Override setRecentsAttachedToAppWindow(boolean attached, boolean animate)262 public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { 263 if (mIsAttachedToWindow == attached && animate) { 264 return; 265 } 266 mActivity.getStateManager() 267 .cancelStateElementAnimation(INDEX_RECENTS_FADE_ANIM); 268 mActivity.getStateManager() 269 .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM); 270 271 AnimatorSet animatorSet = new AnimatorSet(); 272 animatorSet.addListener(new AnimatorListenerAdapter() { 273 @Override 274 public void onAnimationStart(Animator animation) { 275 super.onAnimationStart(animation); 276 mIsAttachedToWindow = attached; 277 if (attached) { 278 mHasEverAttachedToWindow = true; 279 } 280 }}); 281 282 long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0; 283 Animator fadeAnim = mActivity.getStateManager() 284 .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0); 285 fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2); 286 fadeAnim.setDuration(animationDuration); 287 animatorSet.play(fadeAnim); 288 289 float fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get( 290 mActivity.getOverviewPanel()); 291 float toTranslation = attached ? 0 : 1; 292 293 Animator translationAnimator = mActivity.getStateManager().createStateElementAnimation( 294 INDEX_RECENTS_TRANSLATE_X_ANIM, fromTranslation, toTranslation); 295 translationAnimator.setDuration(animationDuration); 296 animatorSet.play(translationAnimator); 297 animatorSet.start(); 298 } 299 300 @Override isRecentsAttachedToAppWindow()301 public boolean isRecentsAttachedToAppWindow() { 302 return mIsAttachedToWindow; 303 } 304 305 @Override hasRecentsEverAttachedToAppWindow()306 public boolean hasRecentsEverAttachedToAppWindow() { 307 return mHasEverAttachedToWindow; 308 } 309 310 @Override setEndTarget(GestureState.GestureEndTarget endTarget)311 public void setEndTarget(GestureState.GestureEndTarget endTarget) { 312 mTargetState = stateFromGestureEndTarget(endTarget); 313 } 314 createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa)315 protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) { 316 // Scale down recents from being full screen to being in overview. 317 RecentsView recentsView = activity.getOverviewPanel(); 318 pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY, 319 recentsView.getMaxScaleForFullScreen(), 1, LINEAR); 320 pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR); 321 322 pa.addListener(new AnimatorListenerAdapter() { 323 @Override 324 public void onAnimationStart(Animator animation) { 325 TaskbarUIController taskbarUIController = getTaskbarController(); 326 if (taskbarUIController != null) { 327 taskbarUIController.setSystemGestureInProgress(true); 328 } 329 } 330 }); 331 } 332 } 333 } 334