• 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.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