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.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; 22 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; 23 import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION; 24 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; 25 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; 26 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM; 27 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM; 28 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; 29 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; 30 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; 31 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; 32 33 import android.animation.Animator; 34 import android.animation.AnimatorListenerAdapter; 35 import android.animation.AnimatorSet; 36 import android.animation.ObjectAnimator; 37 import android.annotation.TargetApi; 38 import android.content.Context; 39 import android.content.res.Resources; 40 import android.graphics.Color; 41 import android.graphics.PointF; 42 import android.graphics.Rect; 43 import android.os.Build; 44 import android.view.Gravity; 45 import android.view.MotionEvent; 46 import android.view.RemoteAnimationTarget; 47 import android.view.View; 48 49 import androidx.annotation.Nullable; 50 import androidx.annotation.UiThread; 51 52 import com.android.launcher3.DeviceProfile; 53 import com.android.launcher3.R; 54 import com.android.launcher3.anim.AnimatorPlaybackController; 55 import com.android.launcher3.anim.PendingAnimation; 56 import com.android.launcher3.config.FeatureFlags; 57 import com.android.launcher3.statehandlers.DepthController; 58 import com.android.launcher3.statehandlers.DesktopVisibilityController; 59 import com.android.launcher3.statemanager.BaseState; 60 import com.android.launcher3.statemanager.StatefulActivity; 61 import com.android.launcher3.taskbar.TaskbarUIController; 62 import com.android.launcher3.touch.PagedOrientationHandler; 63 import com.android.launcher3.util.DisplayController; 64 import com.android.launcher3.util.NavigationMode; 65 import com.android.launcher3.views.ScrimView; 66 import com.android.quickstep.util.ActivityInitListener; 67 import com.android.quickstep.util.AnimatorControllerWithResistance; 68 import com.android.quickstep.views.DesktopTaskView; 69 import com.android.quickstep.views.RecentsView; 70 import com.android.systemui.shared.recents.model.ThumbnailData; 71 72 import java.util.HashMap; 73 import java.util.Optional; 74 import java.util.function.Consumer; 75 import java.util.function.Predicate; 76 77 /** 78 * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. 79 */ 80 @TargetApi(Build.VERSION_CODES.P) 81 public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>, 82 ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> { 83 84 public final boolean rotationSupportedByActivity; 85 86 private final STATE_TYPE mBackgroundState; 87 88 private STATE_TYPE mTargetState; 89 90 @Nullable private Runnable mOnInitBackgroundStateUICallback = null; 91 BaseActivityInterface(boolean rotationSupportedByActivity, STATE_TYPE overviewState, STATE_TYPE backgroundState)92 protected BaseActivityInterface(boolean rotationSupportedByActivity, 93 STATE_TYPE overviewState, STATE_TYPE backgroundState) { 94 this.rotationSupportedByActivity = rotationSupportedByActivity; 95 mTargetState = overviewState; 96 mBackgroundState = backgroundState; 97 } 98 99 /** 100 * Called when the current gesture transition is cancelled. 101 * @param activityVisible Whether the user can see the changes we make here, so try to animate. 102 * @param endTarget If the gesture ended before we got cancelled, where we were headed. 103 */ onTransitionCancelled(boolean activityVisible, @Nullable GestureState.GestureEndTarget endTarget)104 public void onTransitionCancelled(boolean activityVisible, 105 @Nullable GestureState.GestureEndTarget endTarget) { 106 ACTIVITY_TYPE activity = getCreatedActivity(); 107 if (activity == null) { 108 return; 109 } 110 STATE_TYPE startState = activity.getStateManager().getRestState(); 111 if (endTarget != null) { 112 // We were on our way to this state when we got canceled, end there instead. 113 startState = stateFromGestureEndTarget(endTarget); 114 if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) { 115 DesktopVisibilityController controller = getDesktopVisibilityController(); 116 if (controller != null && controller.areFreeformTasksVisible() 117 && endTarget == LAST_TASK) { 118 // When we are cancelling the transition and going back to last task, move to 119 // rest state instead when desktop tasks are visible. 120 // If a fullscreen task is visible, launcher goes to normal state when the 121 // activity is stopped. This does not happen when freeform tasks are visible 122 // on top of launcher. Force the launcher state to rest state here. 123 startState = activity.getStateManager().getRestState(); 124 // Do not animate the transition 125 activityVisible = false; 126 } 127 } 128 } 129 activity.getStateManager().goToState(startState, activityVisible); 130 } 131 getSwipeUpDestinationAndLength( DeviceProfile dp, Context context, Rect outRect, PagedOrientationHandler orientationHandler)132 public abstract int getSwipeUpDestinationAndLength( 133 DeviceProfile dp, Context context, Rect outRect, 134 PagedOrientationHandler orientationHandler); 135 136 /** Called when the animation to home has fully settled. */ onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState)137 public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {} 138 onAssistantVisibilityChanged(float visibility)139 public abstract void onAssistantVisibilityChanged(float visibility); 140 prepareRecentsUI(RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback)141 public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, 142 boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback); 143 createActivityInitListener( Predicate<Boolean> onInitListener)144 public abstract ActivityInitListener createActivityInitListener( 145 Predicate<Boolean> onInitListener); 146 147 /** 148 * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. 149 */ setOnDeferredActivityLaunchCallback(Runnable r)150 public void setOnDeferredActivityLaunchCallback(Runnable r) {} 151 152 @Nullable getCreatedActivity()153 public abstract ACTIVITY_TYPE getCreatedActivity(); 154 155 @Nullable getDepthController()156 public DepthController getDepthController() { 157 return null; 158 } 159 160 @Nullable getDesktopVisibilityController()161 public DesktopVisibilityController getDesktopVisibilityController() { 162 return null; 163 } 164 165 @Nullable getTaskbarController()166 public abstract TaskbarUIController getTaskbarController(); 167 isResumed()168 public final boolean isResumed() { 169 ACTIVITY_TYPE activity = getCreatedActivity(); 170 return activity != null && activity.hasBeenResumed(); 171 } 172 isStarted()173 public final boolean isStarted() { 174 ACTIVITY_TYPE activity = getCreatedActivity(); 175 return activity != null && activity.isStarted(); 176 } 177 178 @UiThread 179 @Nullable getVisibleRecentsView()180 public abstract <T extends RecentsView> T getVisibleRecentsView(); 181 182 @UiThread switchToRecentsIfVisible(Runnable onCompleteCallback)183 public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback); 184 getOverviewWindowBounds( Rect homeBounds, RemoteAnimationTarget target)185 public abstract Rect getOverviewWindowBounds( 186 Rect homeBounds, RemoteAnimationTarget target); 187 allowMinimizeSplitScreen()188 public abstract boolean allowMinimizeSplitScreen(); 189 190 /** @return whether to allow going to All Apps from Overview. */ allowAllAppsFromOverview()191 public abstract boolean allowAllAppsFromOverview(); 192 deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)193 public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { 194 return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons() 195 || isTrackpadMultiFingerSwipe(ev); 196 } 197 198 /** 199 * @return Whether the gesture in progress should be cancelled. 200 */ shouldCancelCurrentGesture()201 public boolean shouldCancelCurrentGesture() { 202 return false; 203 } 204 onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable)205 public abstract void onExitOverview(RotationTouchHelper deviceState, 206 Runnable exitRunnable); 207 isInLiveTileMode()208 public abstract boolean isInLiveTileMode(); 209 onLaunchTaskFailed()210 public abstract void onLaunchTaskFailed(); 211 212 /** 213 * Closes any overlays. 214 */ closeOverlay()215 public void closeOverlay() { 216 Optional.ofNullable(getTaskbarController()).ifPresent( 217 TaskbarUIController::hideOverlayWindow); 218 } 219 switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, Runnable runnable)220 public void switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, 221 Runnable runnable) { 222 ACTIVITY_TYPE activity = getCreatedActivity(); 223 if (activity == null) { 224 return; 225 } 226 RecentsView recentsView = activity.getOverviewPanel(); 227 if (recentsView == null) { 228 if (runnable != null) { 229 runnable.run(); 230 } 231 return; 232 } 233 recentsView.switchToScreenshot(thumbnailDatas, runnable); 234 } 235 236 /** 237 * Calculates the taskView size for the provided device configuration. 238 */ calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, PagedOrientationHandler orientedState)239 public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, 240 PagedOrientationHandler orientedState) { 241 if (dp.isTablet) { 242 if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) { 243 calculateGridTaskSize(context, dp, outRect, orientedState); 244 } else { 245 calculateFocusTaskSize(context, dp, outRect); 246 } 247 } else { 248 Resources res = context.getResources(); 249 float maxScale = res.getFloat(R.dimen.overview_max_scale); 250 int taskMargin = dp.overviewTaskMarginPx; 251 calculateTaskSizeInternal( 252 context, 253 dp, 254 dp.overviewTaskThumbnailTopMarginPx, 255 dp.getOverviewActionsClaimedSpace(), 256 res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin, 257 maxScale, 258 Gravity.CENTER, 259 outRect); 260 } 261 } 262 calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect)263 private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) { 264 Resources res = context.getResources(); 265 float maxScale = res.getFloat(R.dimen.overview_max_scale); 266 Rect gridRect = new Rect(); 267 calculateGridSize(dp, gridRect); 268 calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect); 269 } 270 calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity, Rect outRect)271 private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove, 272 int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity, 273 Rect outRect) { 274 Rect insets = dp.getInsets(); 275 276 Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx); 277 potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom); 278 potentialTaskRect.inset( 279 minimumHorizontalPadding, 280 claimedSpaceAbove, 281 minimumHorizontalPadding, 282 claimedSpaceBelow); 283 284 calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect); 285 } 286 calculateTaskSizeInternal(Context context, DeviceProfile dp, Rect potentialTaskRect, float maxScale, int gravity, Rect outRect)287 private void calculateTaskSizeInternal(Context context, DeviceProfile dp, 288 Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) { 289 PointF taskDimension = getTaskDimension(context, dp); 290 291 float scale = Math.min( 292 potentialTaskRect.width() / taskDimension.x, 293 potentialTaskRect.height() / taskDimension.y); 294 scale = Math.min(scale, maxScale); 295 int outWidth = Math.round(scale * taskDimension.x); 296 int outHeight = Math.round(scale * taskDimension.y); 297 298 Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect); 299 } 300 getTaskDimension(Context context, DeviceProfile dp)301 private static PointF getTaskDimension(Context context, DeviceProfile dp) { 302 PointF dimension = new PointF(); 303 getTaskDimension(context, dp, dimension); 304 return dimension; 305 } 306 307 /** 308 * Gets the dimension of the task in the current system state. 309 */ getTaskDimension(Context context, DeviceProfile dp, PointF out)310 public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) { 311 out.x = dp.widthPx; 312 out.y = dp.heightPx; 313 if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) { 314 out.y -= dp.taskbarHeight; 315 } 316 } 317 318 /** 319 * Calculates the overview grid size for the provided device configuration. 320 */ calculateGridSize(DeviceProfile dp, Rect outRect)321 public final void calculateGridSize(DeviceProfile dp, Rect outRect) { 322 Rect insets = dp.getInsets(); 323 int topMargin = dp.overviewTaskThumbnailTopMarginPx; 324 int bottomMargin = dp.getOverviewActionsClaimedSpace(); 325 int sideMargin = dp.overviewGridSideMargin; 326 327 outRect.set(0, 0, dp.widthPx, dp.heightPx); 328 outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin, 329 Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin)); 330 } 331 332 /** 333 * Calculates the overview grid non-focused task size for the provided device configuration. 334 */ calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect, PagedOrientationHandler orientedState)335 public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect, 336 PagedOrientationHandler orientedState) { 337 Resources res = context.getResources(); 338 Rect potentialTaskRect = new Rect(); 339 if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) { 340 calculateGridSize(dp, potentialTaskRect); 341 } else { 342 calculateFocusTaskSize(context, dp, potentialTaskRect); 343 } 344 345 float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx 346 - dp.overviewRowSpacing) / 2f; 347 348 PointF taskDimension = getTaskDimension(context, dp); 349 float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y; 350 int outWidth = Math.round(scale * taskDimension.x); 351 int outHeight = Math.round(scale * taskDimension.y); 352 353 int gravity = Gravity.TOP; 354 gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT; 355 Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect); 356 } 357 358 /** 359 * Calculates the task size for the desktop task 360 */ calculateDesktopTaskSize(Context context, DeviceProfile dp, Rect outRect)361 public final void calculateDesktopTaskSize(Context context, DeviceProfile dp, Rect outRect) { 362 calculateFocusTaskSize(context, dp, outRect); 363 } 364 365 /** 366 * Calculates the modal taskView size for the provided device configuration 367 */ calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect, PagedOrientationHandler orientedState)368 public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect, 369 PagedOrientationHandler orientedState) { 370 calculateTaskSize(context, dp, outRect, orientedState); 371 boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get(); 372 int claimedSpaceBelow = isGridOnlyOverview 373 ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight 374 : (dp.heightPx - outRect.bottom - dp.getInsets().bottom); 375 int minimumHorizontalPadding = 0; 376 if (!isGridOnlyOverview) { 377 float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale); 378 minimumHorizontalPadding = 379 Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2); 380 } 381 calculateTaskSizeInternal( 382 context, 383 dp, 384 dp.overviewTaskMarginPx, 385 claimedSpaceBelow, 386 minimumHorizontalPadding, 387 1f /*maxScale*/, 388 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 389 outRect); 390 } 391 392 /** 393 * Called when the gesture ends and the animation starts towards the given target. Used to add 394 * an optional additional animation with the same duration. 395 */ getParallelAnimationToLauncher( GestureState.GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks)396 public @Nullable Animator getParallelAnimationToLauncher( 397 GestureState.GestureEndTarget endTarget, long duration, 398 RecentsAnimationCallbacks callbacks) { 399 if (endTarget == RECENTS) { 400 ACTIVITY_TYPE activity = getCreatedActivity(); 401 if (activity == null) { 402 return null; 403 } 404 STATE_TYPE state = stateFromGestureEndTarget(endTarget); 405 ScrimView scrimView = activity.getScrimView(); 406 ObjectAnimator anim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR, 407 getOverviewScrimColorForState(activity, state)); 408 anim.setDuration(duration); 409 return anim; 410 } 411 return null; 412 } 413 414 /** 415 * Returns the color of the scrim behind overview when at rest in this state. 416 * Return {@link Color#TRANSPARENT} for no scrim. 417 */ getOverviewScrimColorForState(ACTIVITY_TYPE activity, STATE_TYPE state)418 protected abstract int getOverviewScrimColorForState(ACTIVITY_TYPE activity, STATE_TYPE state); 419 420 /** 421 * Returns the expected STATE_TYPE from the provided GestureEndTarget. 422 */ stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget)423 public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget); 424 425 /** 426 * Called when the animation to the target has finished, but right before updating the state. 427 * @return A View that needs to draw before ending the recents animation to LAST_TASK. 428 * (This is a hack to ensure Taskbar draws its background first to avoid flickering.) 429 */ onSettledOnEndTarget(GestureState.GestureEndTarget endTarget)430 public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) { 431 TaskbarUIController taskbarUIController = getTaskbarController(); 432 if (taskbarUIController != null) { 433 taskbarUIController.setSystemGestureInProgress(false); 434 return taskbarUIController.getRootView(); 435 } 436 return null; 437 } 438 runOnInitBackgroundStateUI(Runnable callback)439 protected void runOnInitBackgroundStateUI(Runnable callback) { 440 mOnInitBackgroundStateUICallback = callback; 441 ACTIVITY_TYPE activity = getCreatedActivity(); 442 if (activity != null && activity.getStateManager().getState() == mBackgroundState) { 443 onInitBackgroundStateUI(); 444 } 445 } 446 onInitBackgroundStateUI()447 private void onInitBackgroundStateUI() { 448 if (mOnInitBackgroundStateUICallback != null) { 449 mOnInitBackgroundStateUICallback.run(); 450 mOnInitBackgroundStateUICallback = null; 451 } 452 } 453 454 public interface AnimationFactory { 455 createActivityInterface(long transitionLength)456 void createActivityInterface(long transitionLength); 457 458 /** 459 * @param attached Whether to show RecentsView alongside the app window. If false, recents 460 * will be hidden by some property we can animate, e.g. alpha. 461 * @param animate Whether to animate recents to/from its new attached state. 462 */ setRecentsAttachedToAppWindow(boolean attached, boolean animate)463 default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { } 464 isRecentsAttachedToAppWindow()465 default boolean isRecentsAttachedToAppWindow() { 466 return false; 467 } 468 hasRecentsEverAttachedToAppWindow()469 default boolean hasRecentsEverAttachedToAppWindow() { 470 return false; 471 } 472 473 /** Called when the gesture ends and we know what state it is going towards */ setEndTarget(GestureState.GestureEndTarget endTarget)474 default void setEndTarget(GestureState.GestureEndTarget endTarget) { } 475 } 476 477 class DefaultAnimationFactory implements AnimationFactory { 478 479 protected final ACTIVITY_TYPE mActivity; 480 private final STATE_TYPE mStartState; 481 private final Consumer<AnimatorControllerWithResistance> mCallback; 482 483 private boolean mIsAttachedToWindow; 484 private boolean mHasEverAttachedToWindow; 485 DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback)486 DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) { 487 mCallback = callback; 488 489 mActivity = getCreatedActivity(); 490 mStartState = mActivity.getStateManager().getState(); 491 } 492 initBackgroundStateUI()493 protected ACTIVITY_TYPE initBackgroundStateUI() { 494 STATE_TYPE resetState = mStartState; 495 if (mStartState.shouldDisableRestore()) { 496 resetState = mActivity.getStateManager().getRestState(); 497 } 498 mActivity.getStateManager().setRestState(resetState); 499 mActivity.getStateManager().goToState(mBackgroundState, false); 500 onInitBackgroundStateUI(); 501 return mActivity; 502 } 503 504 @Override createActivityInterface(long transitionLength)505 public void createActivityInterface(long transitionLength) { 506 PendingAnimation pa = new PendingAnimation(transitionLength * 2); 507 createBackgroundToOverviewAnim(mActivity, pa); 508 AnimatorPlaybackController controller = pa.createPlaybackController(); 509 mActivity.getStateManager().setCurrentUserControlledAnimation(controller); 510 511 // Since we are changing the start position of the UI, reapply the state, at the end 512 controller.setEndAction(() -> mActivity.getStateManager().goToState( 513 controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState, 514 false)); 515 516 RecentsView recentsView = mActivity.getOverviewPanel(); 517 AnimatorControllerWithResistance controllerWithResistance = 518 AnimatorControllerWithResistance.createForRecents(controller, mActivity, 519 recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(), 520 recentsView, RECENTS_SCALE_PROPERTY, recentsView, 521 TASK_SECONDARY_TRANSLATION); 522 mCallback.accept(controllerWithResistance); 523 524 // Creating the activity controller animation sometimes reapplies the launcher state 525 // (because we set the animation as the current state animation), so we reapply the 526 // attached state here as well to ensure recents is shown/hidden appropriately. 527 if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) { 528 setRecentsAttachedToAppWindow(mIsAttachedToWindow, false); 529 } 530 } 531 532 @Override setRecentsAttachedToAppWindow(boolean attached, boolean animate)533 public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { 534 if (mIsAttachedToWindow == attached && animate) { 535 return; 536 } 537 mActivity.getStateManager() 538 .cancelStateElementAnimation(INDEX_RECENTS_FADE_ANIM); 539 mActivity.getStateManager() 540 .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM); 541 542 AnimatorSet animatorSet = new AnimatorSet(); 543 animatorSet.addListener(new AnimatorListenerAdapter() { 544 @Override 545 public void onAnimationStart(Animator animation) { 546 super.onAnimationStart(animation); 547 mIsAttachedToWindow = attached; 548 if (attached) { 549 mHasEverAttachedToWindow = true; 550 } 551 }}); 552 553 long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0; 554 Animator fadeAnim = mActivity.getStateManager() 555 .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0); 556 fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2); 557 fadeAnim.setDuration(animationDuration); 558 animatorSet.play(fadeAnim); 559 560 float fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get( 561 mActivity.getOverviewPanel()); 562 float toTranslation = attached ? 0 : 1; 563 564 Animator translationAnimator = mActivity.getStateManager().createStateElementAnimation( 565 INDEX_RECENTS_TRANSLATE_X_ANIM, fromTranslation, toTranslation); 566 translationAnimator.setDuration(animationDuration); 567 animatorSet.play(translationAnimator); 568 animatorSet.start(); 569 } 570 571 @Override isRecentsAttachedToAppWindow()572 public boolean isRecentsAttachedToAppWindow() { 573 return mIsAttachedToWindow; 574 } 575 576 @Override hasRecentsEverAttachedToAppWindow()577 public boolean hasRecentsEverAttachedToAppWindow() { 578 return mHasEverAttachedToWindow; 579 } 580 581 @Override setEndTarget(GestureState.GestureEndTarget endTarget)582 public void setEndTarget(GestureState.GestureEndTarget endTarget) { 583 mTargetState = stateFromGestureEndTarget(endTarget); 584 } 585 createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa)586 protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) { 587 // Scale down recents from being full screen to being in overview. 588 RecentsView recentsView = activity.getOverviewPanel(); 589 pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY, 590 recentsView.getMaxScaleForFullScreen(), 1, LINEAR); 591 pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR); 592 593 pa.addListener(new AnimatorListenerAdapter() { 594 @Override 595 public void onAnimationStart(Animator animation) { 596 TaskbarUIController taskbarUIController = getTaskbarController(); 597 if (taskbarUIController != null) { 598 taskbarUIController.setSystemGestureInProgress(true); 599 } 600 } 601 }); 602 } 603 } 604 } 605