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