1/*
2 * Copyright (C) 2015 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
17package com.android.launcher3;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.animation.PropertyValuesHolder;
24import android.animation.TimeInterpolator;
25import android.animation.ValueAnimator;
26import android.content.res.Resources;
27import android.util.Log;
28import android.view.View;
29import android.view.animation.AccelerateInterpolator;
30
31import com.android.launcher3.allapps.AllAppsContainerView;
32import com.android.launcher3.allapps.AllAppsTransitionController;
33import com.android.launcher3.anim.AnimationLayerSet;
34import com.android.launcher3.config.FeatureFlags;
35import com.android.launcher3.util.CircleRevealOutlineProvider;
36import com.android.launcher3.util.Thunk;
37import com.android.launcher3.widget.WidgetsContainerView;
38
39/**
40 * TODO: figure out what kind of tests we can write for this
41 *
42 * Things to test when changing the following class.
43 *   - Home from workspace
44 *          - from center screen
45 *          - from other screens
46 *   - Home from all apps
47 *          - from center screen
48 *          - from other screens
49 *   - Back from all apps
50 *          - from center screen
51 *          - from other screens
52 *   - Launch app from workspace and quit
53 *          - with back
54 *          - with home
55 *   - Launch app from all apps and quit
56 *          - with back
57 *          - with home
58 *   - Go to a screen that's not the default, then all
59 *     apps, and launch and app, and go back
60 *          - with back
61 *          -with home
62 *   - On workspace, long press power and go back
63 *          - with back
64 *          - with home
65 *   - On all apps, long press power and go back
66 *          - with back
67 *          - with home
68 *   - On workspace, power off
69 *   - On all apps, power off
70 *   - Launch an app and turn off the screen while in that app
71 *          - Go back with home key
72 *          - Go back with back key  TODO: make this not go to workspace
73 *          - From all apps
74 *          - From workspace
75 *   - Enter and exit car mode (becuase it causes an extra configuration changed)
76 *          - From all apps
77 *          - From the center workspace
78 *          - From another workspace
79 */
80public class LauncherStateTransitionAnimation {
81
82    /**
83     * animation used for all apps and widget tray when
84     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
85     */
86    public static final int CIRCULAR_REVEAL = 0;
87    /**
88     * animation used for all apps and not widget tray when
89     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
90     */
91    public static final int PULLUP = 1;
92
93    private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
94
95    /**
96     * Private callbacks made during transition setup.
97     */
98    private static class PrivateTransitionCallbacks {
99        private final float materialRevealViewFinalAlpha;
100
101        PrivateTransitionCallbacks(float revealAlpha) {
102            materialRevealViewFinalAlpha = revealAlpha;
103        }
104
105        float getMaterialRevealViewStartFinalRadius() {
106            return 0;
107        }
108        AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
109                View buttonView) {
110            return null;
111        }
112        void onTransitionComplete() {}
113    }
114
115    public static final String TAG = "LSTAnimation";
116
117    public static final int SINGLE_FRAME_DELAY = 16;
118
119    @Thunk Launcher mLauncher;
120    @Thunk AnimatorSet mCurrentAnimation;
121    AllAppsTransitionController mAllAppsController;
122
123    public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
124        mLauncher = l;
125        mAllAppsController = allAppsController;
126    }
127
128    /**
129     * Starts an animation to the apps view.
130     *
131     * @param startSearchAfterTransition Immediately starts app search after the transition to
132     *                                   All Apps is completed.
133     */
134    public void startAnimationToAllApps(
135            final boolean animated, final boolean startSearchAfterTransition) {
136        final AllAppsContainerView toView = mLauncher.getAppsView();
137        final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
138        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
139            @Override
140            public float getMaterialRevealViewStartFinalRadius() {
141                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
142                return allAppsButtonSize / 2;
143            }
144            @Override
145            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
146                    final View revealView, final View allAppsButtonView) {
147                return new AnimatorListenerAdapter() {
148                    public void onAnimationStart(Animator animation) {
149                        allAppsButtonView.setVisibility(View.INVISIBLE);
150                    }
151                    public void onAnimationEnd(Animator animation) {
152                        allAppsButtonView.setVisibility(View.VISIBLE);
153                    }
154                };
155            }
156            @Override
157            void onTransitionComplete() {
158                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
159                if (startSearchAfterTransition) {
160                    toView.startAppsSearch();
161                }
162            }
163        };
164        int animType = CIRCULAR_REVEAL;
165        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
166            animType = PULLUP;
167        }
168        // Only animate the search bar if animating from spring loaded mode back to all apps
169        startAnimationToOverlay(
170                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
171    }
172
173    /**
174     * Starts an animation to the widgets view.
175     */
176    public void startAnimationToWidgets(final boolean animated) {
177        final WidgetsContainerView toView = mLauncher.getWidgetsView();
178        final View buttonView = mLauncher.getWidgetsButton();
179        startAnimationToOverlay(
180                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
181                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
182                    @Override
183                    void onTransitionComplete() {
184                        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
185                    }
186                });
187    }
188
189    /**
190     * Starts an animation to the workspace from the current overlay view.
191     */
192    public void startAnimationToWorkspace(final Launcher.State fromState,
193            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
194            final boolean animated, final Runnable onCompleteRunnable) {
195        if (toWorkspaceState != Workspace.State.NORMAL &&
196                toWorkspaceState != Workspace.State.SPRING_LOADED &&
197                toWorkspaceState != Workspace.State.OVERVIEW) {
198            Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
199        }
200
201        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
202                || mAllAppsController.isTransitioning()) {
203            int animType = CIRCULAR_REVEAL;
204            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
205                animType = PULLUP;
206            }
207            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
208                    animated, animType, onCompleteRunnable);
209        } else if (fromState == Launcher.State.WIDGETS ||
210                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
211            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
212                    animated, onCompleteRunnable);
213        } else {
214            startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
215                    animated, onCompleteRunnable);
216        }
217    }
218
219    /**
220     * Creates and starts a new animation to a particular overlay view.
221     */
222    private void startAnimationToOverlay(
223            final Workspace.State toWorkspaceState,
224            final View buttonView, final BaseContainerView toView,
225            final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
226        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
227        final Resources res = mLauncher.getResources();
228        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
229        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
230
231        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
232
233        final AnimationLayerSet layerViews = new AnimationLayerSet();
234
235        // If for some reason our views aren't initialized, don't animate
236        boolean initialized = buttonView != null;
237
238        // Cancel the current animation
239        cancelAnimation();
240
241        final View contentView = toView.getContentView();
242        playCommonTransitionAnimations(toWorkspaceState,
243                animated, initialized, animation, layerViews);
244        if (!animated || !initialized) {
245            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
246                    toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
247                mAllAppsController.finishPullUp();
248            }
249            toView.setTranslationX(0.0f);
250            toView.setTranslationY(0.0f);
251            toView.setScaleX(1.0f);
252            toView.setScaleY(1.0f);
253            toView.setAlpha(1.0f);
254            toView.setVisibility(View.VISIBLE);
255
256            // Show the content view
257            contentView.setVisibility(View.VISIBLE);
258            pCb.onTransitionComplete();
259            return;
260        }
261        if (animType == CIRCULAR_REVEAL) {
262            // Setup the reveal view animation
263            final View revealView = toView.getRevealView();
264
265            int width = revealView.getMeasuredWidth();
266            int height = revealView.getMeasuredHeight();
267            float revealRadius = (float) Math.hypot(width / 2, height / 2);
268            revealView.setVisibility(View.VISIBLE);
269            revealView.setAlpha(0f);
270            revealView.setTranslationY(0f);
271            revealView.setTranslationX(0f);
272
273            // Calculate the final animation values
274            int[] buttonViewToPanelDelta =
275                    Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
276            final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
277            final float revealViewToXDrift = buttonViewToPanelDelta[0];
278            final float revealViewToYDrift = buttonViewToPanelDelta[1];
279
280            // Create the animators
281            PropertyValuesHolder panelAlpha =
282                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
283            PropertyValuesHolder panelDriftY =
284                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
285            PropertyValuesHolder panelDriftX =
286                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
287            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
288                    panelAlpha, panelDriftY, panelDriftX);
289            panelAlphaAndDrift.setDuration(revealDuration);
290            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
291
292            // Play the animation
293            layerViews.addView(revealView);
294            animation.play(panelAlphaAndDrift);
295
296            // Setup the animation for the content view
297            contentView.setVisibility(View.VISIBLE);
298            contentView.setAlpha(0f);
299            contentView.setTranslationY(revealViewToYDrift);
300            layerViews.addView(contentView);
301
302            // Create the individual animators
303            ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
304                    revealViewToYDrift, 0);
305            pageDrift.setDuration(revealDuration);
306            pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
307            pageDrift.setStartDelay(itemsAlphaStagger);
308            animation.play(pageDrift);
309
310            ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
311            itemsAlpha.setDuration(revealDuration);
312            itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
313            itemsAlpha.setStartDelay(itemsAlphaStagger);
314            animation.play(itemsAlpha);
315
316            float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
317            AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
318                    revealView, buttonView);
319            Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
320                    startRadius, revealRadius).createRevealAnimator(revealView);
321            reveal.setDuration(revealDuration);
322            reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
323            if (listener != null) {
324                reveal.addListener(listener);
325            }
326            animation.play(reveal);
327
328            animation.addListener(new AnimatorListenerAdapter() {
329                @Override
330                public void onAnimationEnd(Animator animation) {
331                    // Hide the reveal view
332                    revealView.setVisibility(View.INVISIBLE);
333
334                    // This can hold unnecessary references to views.
335                    cleanupAnimation();
336                    pCb.onTransitionComplete();
337                }
338
339            });
340
341            toView.bringToFront();
342            toView.setVisibility(View.VISIBLE);
343
344            animation.addListener(layerViews);
345            toView.post(new StartAnimRunnable(animation, toView));
346            mCurrentAnimation = animation;
347        } else if (animType == PULLUP) {
348            // We are animating the content view alpha, so ensure we have a layer for it
349            layerViews.addView(contentView);
350
351            animation.addListener(new AnimatorListenerAdapter() {
352                @Override
353                public void onAnimationEnd(Animator animation) {
354                    cleanupAnimation();
355                    pCb.onTransitionComplete();
356                }
357            });
358            boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
359
360            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
361            mCurrentAnimation = animation;
362            mCurrentAnimation.addListener(layerViews);
363            if (shouldPost) {
364                toView.post(startAnimRunnable);
365            } else {
366                startAnimRunnable.run();
367            }
368        }
369    }
370
371    /**
372     * Plays animations used by various transitions.
373     */
374    private void playCommonTransitionAnimations(
375            Workspace.State toWorkspaceState,
376            boolean animated, boolean initialized, AnimatorSet animation,
377            AnimationLayerSet layerViews) {
378        // Create the workspace animation.
379        // NOTE: this call apparently also sets the state for the workspace if !animated
380        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
381                animated, layerViews);
382
383        if (animated && initialized) {
384            // Play the workspace animation
385            if (workspaceAnim != null) {
386                animation.play(workspaceAnim);
387            }
388        }
389    }
390
391    /**
392     * Starts an animation to the workspace from the apps view.
393     */
394    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
395            final Workspace.State toWorkspaceState, final boolean animated, int type,
396            final Runnable onCompleteRunnable) {
397        final AllAppsContainerView appsView = mLauncher.getAppsView();
398        // No alpha anim from all apps
399        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
400            @Override
401            float getMaterialRevealViewStartFinalRadius() {
402                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
403                return allAppsButtonSize / 2;
404            }
405            @Override
406            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
407                    final View revealView, final View allAppsButtonView) {
408                return new AnimatorListenerAdapter() {
409                    public void onAnimationStart(Animator animation) {
410                        // We set the alpha instead of visibility to ensure that the focus does not
411                        // get taken from the all apps view
412                        allAppsButtonView.setVisibility(View.VISIBLE);
413                        allAppsButtonView.setAlpha(0f);
414                    }
415                    public void onAnimationEnd(Animator animation) {
416                        // Hide the reveal view
417                        revealView.setVisibility(View.INVISIBLE);
418
419                        // Show the all apps button, and focus it
420                        allAppsButtonView.setAlpha(1f);
421                    }
422                };
423            }
424            @Override
425            void onTransitionComplete() {
426                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
427                appsView.reset();
428            }
429        };
430        // Only animate the search bar if animating to spring loaded mode from all apps
431        startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
432                mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
433                animated, type, onCompleteRunnable, cb);
434    }
435
436    /**
437     * Starts an animation to the workspace from the widgets view.
438     */
439    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
440            final Workspace.State toWorkspaceState, final boolean animated,
441            final Runnable onCompleteRunnable) {
442        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
443        PrivateTransitionCallbacks cb =
444                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
445            @Override
446            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
447                    final View revealView, final View widgetsButtonView) {
448                return new AnimatorListenerAdapter() {
449                    public void onAnimationEnd(Animator animation) {
450                        // Hide the reveal view
451                        revealView.setVisibility(View.INVISIBLE);
452                    }
453                };
454            }
455            @Override
456            void onTransitionComplete() {
457                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
458            }
459        };
460        startAnimationToWorkspaceFromOverlay(
461                fromWorkspaceState, toWorkspaceState,
462                mLauncher.getWidgetsButton(), widgetsView,
463                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
464    }
465
466    /**
467     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
468     */
469    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
470            final Workspace.State toWorkspaceState, final boolean animated,
471            final Runnable onCompleteRunnable) {
472        final View fromWorkspace = mLauncher.getWorkspace();
473        final AnimationLayerSet layerViews = new AnimationLayerSet();
474        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
475
476        // Cancel the current animation
477        cancelAnimation();
478
479        playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
480        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
481
482        if (animated) {
483            animation.addListener(new AnimatorListenerAdapter() {
484                @Override
485                public void onAnimationEnd(Animator animation) {
486                    // Run any queued runnables
487                    if (onCompleteRunnable != null) {
488                        onCompleteRunnable.run();
489                    }
490
491                    // This can hold unnecessary references to views.
492                    cleanupAnimation();
493                }
494            });
495            animation.addListener(layerViews);
496            fromWorkspace.post(new StartAnimRunnable(animation, null));
497            mCurrentAnimation = animation;
498        } else /* if (!animated) */ {
499            // Run any queued runnables
500            if (onCompleteRunnable != null) {
501                onCompleteRunnable.run();
502            }
503
504            mCurrentAnimation = null;
505        }
506    }
507
508    /**
509     * Creates and starts a new animation to the workspace.
510     */
511    private void startAnimationToWorkspaceFromOverlay(
512            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
513            final View buttonView, final BaseContainerView fromView,
514            final boolean animated, int animType, final Runnable onCompleteRunnable,
515            final PrivateTransitionCallbacks pCb) {
516        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
517        final Resources res = mLauncher.getResources();
518        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
519        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
520        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
521
522        final View toView = mLauncher.getWorkspace();
523        final View revealView = fromView.getRevealView();
524        final View contentView = fromView.getContentView();
525
526        final AnimationLayerSet layerViews = new AnimationLayerSet();
527
528        // If for some reason our views aren't initialized, don't animate
529        boolean initialized = buttonView != null;
530
531        // Cancel the current animation
532        cancelAnimation();
533
534        playCommonTransitionAnimations(toWorkspaceState,
535                animated, initialized, animation, layerViews);
536        if (!animated || !initialized) {
537            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
538                    fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
539                mAllAppsController.finishPullDown();
540            }
541            fromView.setVisibility(View.GONE);
542            pCb.onTransitionComplete();
543
544            // Run any queued runnables
545            if (onCompleteRunnable != null) {
546                onCompleteRunnable.run();
547            }
548            return;
549        }
550        if (animType == CIRCULAR_REVEAL) {
551            // hideAppsCustomizeHelper is called in some cases when it is already hidden
552            // don't perform all these no-op animations. In particularly, this was causing
553            // the all-apps button to pop in and out.
554            if (fromView.getVisibility() == View.VISIBLE) {
555                int width = revealView.getMeasuredWidth();
556                int height = revealView.getMeasuredHeight();
557                float revealRadius = (float) Math.hypot(width / 2, height / 2);
558                revealView.setVisibility(View.VISIBLE);
559                revealView.setAlpha(1f);
560                revealView.setTranslationY(0);
561                layerViews.addView(revealView);
562
563                // Calculate the final animation values
564                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
565                final float revealViewToXDrift = buttonViewToPanelDelta[0];
566                final float revealViewToYDrift = buttonViewToPanelDelta[1];
567
568                // The vertical motion of the apps panel should be delayed by one frame
569                // from the conceal animation in order to give the right feel. We correspondingly
570                // shorten the duration so that the slide and conceal end at the same time.
571                TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
572                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
573                        0, revealViewToYDrift);
574                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
575                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
576                panelDriftY.setInterpolator(decelerateInterpolator);
577                animation.play(panelDriftY);
578
579                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
580                        0, revealViewToXDrift);
581                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
582                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
583                panelDriftX.setInterpolator(decelerateInterpolator);
584                animation.play(panelDriftX);
585
586                // Setup animation for the reveal panel alpha
587                if (pCb.materialRevealViewFinalAlpha != 1f) {
588                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
589                            1f, pCb.materialRevealViewFinalAlpha);
590                    panelAlpha.setDuration(revealDuration);
591                    panelAlpha.setInterpolator(decelerateInterpolator);
592                    animation.play(panelAlpha);
593                }
594
595                // Setup the animation for the content view
596                layerViews.addView(contentView);
597
598                // Create the individual animators
599                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
600                        0, revealViewToYDrift);
601                contentView.setTranslationY(0);
602                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
603                pageDrift.setInterpolator(decelerateInterpolator);
604                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
605                animation.play(pageDrift);
606
607                contentView.setAlpha(1f);
608                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
609                itemsAlpha.setDuration(100);
610                itemsAlpha.setInterpolator(decelerateInterpolator);
611                animation.play(itemsAlpha);
612
613                // Invalidate the scrim throughout the animation to ensure the highlight
614                // cutout is correct throughout.
615                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
616                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
617                    @Override
618                    public void onAnimationUpdate(ValueAnimator animation) {
619                        mLauncher.getDragLayer().invalidateScrim();
620                    }
621                });
622                animation.play(invalidateScrim);
623
624                // Animate the all apps button
625                float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
626                AnimatorListenerAdapter listener =
627                        pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
628                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
629                        revealRadius, finalRadius).createRevealAnimator(revealView);
630                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
631                reveal.setDuration(revealDuration);
632                reveal.setStartDelay(itemsAlphaStagger);
633                if (listener != null) {
634                    reveal.addListener(listener);
635                }
636                animation.play(reveal);
637            }
638
639            animation.addListener(new AnimatorListenerAdapter() {
640                @Override
641                public void onAnimationEnd(Animator animation) {
642                    fromView.setVisibility(View.GONE);
643                    // Run any queued runnables
644                    if (onCompleteRunnable != null) {
645                        onCompleteRunnable.run();
646                    }
647
648                    // Reset page transforms
649                    if (contentView != null) {
650                        contentView.setTranslationX(0);
651                        contentView.setTranslationY(0);
652                        contentView.setAlpha(1);
653                    }
654
655                    // This can hold unnecessary references to views.
656                    cleanupAnimation();
657                    pCb.onTransitionComplete();
658                }
659            });
660
661            mCurrentAnimation = animation;
662            mCurrentAnimation.addListener(layerViews);
663            fromView.post(new StartAnimRunnable(animation, null));
664        } else if (animType == PULLUP) {
665            // We are animating the content view alpha, so ensure we have a layer for it
666            layerViews.addView(contentView);
667
668            animation.addListener(new AnimatorListenerAdapter() {
669                boolean canceled = false;
670                @Override
671                public void onAnimationCancel(Animator animation) {
672                    canceled = true;
673                }
674
675                @Override
676                public void onAnimationEnd(Animator animation) {
677                    if (canceled) return;
678                    // Run any queued runnables
679                    if (onCompleteRunnable != null) {
680                        onCompleteRunnable.run();
681                    }
682
683                    cleanupAnimation();
684                    pCb.onTransitionComplete();
685                }
686
687            });
688            boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
689
690            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
691            mCurrentAnimation = animation;
692            mCurrentAnimation.addListener(layerViews);
693            if (shouldPost) {
694                fromView.post(startAnimRunnable);
695            } else {
696                startAnimRunnable.run();
697            }
698        }
699        return;
700    }
701
702    /**
703     * Cancels the current animation.
704     */
705    private void cancelAnimation() {
706        if (mCurrentAnimation != null) {
707            mCurrentAnimation.setDuration(0);
708            mCurrentAnimation.cancel();
709            mCurrentAnimation = null;
710        }
711    }
712
713    @Thunk void cleanupAnimation() {
714        mCurrentAnimation = null;
715    }
716
717    private class StartAnimRunnable implements Runnable {
718
719        private final AnimatorSet mAnim;
720        private final View mViewToFocus;
721
722        public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
723            mAnim = anim;
724            mViewToFocus = viewToFocus;
725        }
726
727        @Override
728        public void run() {
729            if (mCurrentAnimation != mAnim) {
730                return;
731            }
732            if (mViewToFocus != null) {
733                mViewToFocus.requestFocus();
734            }
735            mAnim.start();
736        }
737    }
738}
739