• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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