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