LauncherStateTransitionAnimation.java revision 08d8bb9cde705ef66561fb2dbed0af7af2632812
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                    revealView.setAlpha(1f);
339                    revealView.setTranslationX(0f);
340                    revealView.setTranslationY(0f);
341                    contentView.setAlpha(1f);
342                    contentView.setTranslationY(0f);
343                }
344
345            });
346
347            toView.bringToFront();
348            toView.setVisibility(View.VISIBLE);
349
350            animation.addListener(layerViews);
351            toView.post(new StartAnimRunnable(animation, toView));
352            mCurrentAnimation = animation;
353        } else if (animType == PULLUP) {
354            // We are animating the content view alpha, so ensure we have a layer for it
355            layerViews.addView(contentView);
356
357            animation.addListener(new AnimatorListenerAdapter() {
358                @Override
359                public void onAnimationEnd(Animator animation) {
360                    cleanupAnimation();
361                    pCb.onTransitionComplete();
362                }
363            });
364            boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
365
366            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
367            mCurrentAnimation = animation;
368            mCurrentAnimation.addListener(layerViews);
369            if (shouldPost) {
370                toView.post(startAnimRunnable);
371            } else {
372                startAnimRunnable.run();
373            }
374        }
375    }
376
377    /**
378     * Plays animations used by various transitions.
379     */
380    private void playCommonTransitionAnimations(
381            Workspace.State toWorkspaceState,
382            boolean animated, boolean initialized, AnimatorSet animation,
383            AnimationLayerSet layerViews) {
384        // Create the workspace animation.
385        // NOTE: this call apparently also sets the state for the workspace if !animated
386        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
387                animated, layerViews);
388
389        if (animated && initialized) {
390            // Play the workspace animation
391            if (workspaceAnim != null) {
392                animation.play(workspaceAnim);
393            }
394        }
395    }
396
397    /**
398     * Starts an animation to the workspace from the apps view.
399     */
400    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
401            final Workspace.State toWorkspaceState, final boolean animated, int type,
402            final Runnable onCompleteRunnable) {
403        final AllAppsContainerView appsView = mLauncher.getAppsView();
404        // No alpha anim from all apps
405        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
406            @Override
407            float getMaterialRevealViewStartFinalRadius() {
408                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
409                return allAppsButtonSize / 2;
410            }
411            @Override
412            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
413                    final View revealView, final View allAppsButtonView) {
414                return new AnimatorListenerAdapter() {
415                    public void onAnimationStart(Animator animation) {
416                        // We set the alpha instead of visibility to ensure that the focus does not
417                        // get taken from the all apps view
418                        allAppsButtonView.setVisibility(View.VISIBLE);
419                        allAppsButtonView.setAlpha(0f);
420                    }
421                    public void onAnimationEnd(Animator animation) {
422                        // Hide the reveal view
423                        revealView.setVisibility(View.INVISIBLE);
424
425                        // Show the all apps button, and focus it
426                        allAppsButtonView.setAlpha(1f);
427                    }
428                };
429            }
430            @Override
431            void onTransitionComplete() {
432                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
433                appsView.reset();
434            }
435        };
436        // Only animate the search bar if animating to spring loaded mode from all apps
437        startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
438                mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
439                animated, type, onCompleteRunnable, cb);
440    }
441
442    /**
443     * Starts an animation to the workspace from the widgets view.
444     */
445    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
446            final Workspace.State toWorkspaceState, final boolean animated,
447            final Runnable onCompleteRunnable) {
448        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
449        PrivateTransitionCallbacks cb =
450                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
451            @Override
452            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
453                    final View revealView, final View widgetsButtonView) {
454                return new AnimatorListenerAdapter() {
455                    public void onAnimationEnd(Animator animation) {
456                        // Hide the reveal view
457                        revealView.setVisibility(View.INVISIBLE);
458                    }
459                };
460            }
461            @Override
462            void onTransitionComplete() {
463                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
464            }
465        };
466        startAnimationToWorkspaceFromOverlay(
467                fromWorkspaceState, toWorkspaceState,
468                mLauncher.getWidgetsButton(), widgetsView,
469                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
470    }
471
472    /**
473     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
474     */
475    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
476            final Workspace.State toWorkspaceState, final boolean animated,
477            final Runnable onCompleteRunnable) {
478        final View fromWorkspace = mLauncher.getWorkspace();
479        final AnimationLayerSet layerViews = new AnimationLayerSet();
480        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
481
482        // Cancel the current animation
483        cancelAnimation();
484
485        playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
486        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
487
488        if (animated) {
489            animation.addListener(new AnimatorListenerAdapter() {
490                @Override
491                public void onAnimationEnd(Animator animation) {
492                    // Run any queued runnables
493                    if (onCompleteRunnable != null) {
494                        onCompleteRunnable.run();
495                    }
496
497                    // This can hold unnecessary references to views.
498                    cleanupAnimation();
499                }
500            });
501            animation.addListener(layerViews);
502            fromWorkspace.post(new StartAnimRunnable(animation, null));
503            mCurrentAnimation = animation;
504        } else /* if (!animated) */ {
505            // Run any queued runnables
506            if (onCompleteRunnable != null) {
507                onCompleteRunnable.run();
508            }
509
510            mCurrentAnimation = null;
511        }
512    }
513
514    /**
515     * Creates and starts a new animation to the workspace.
516     */
517    private void startAnimationToWorkspaceFromOverlay(
518            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
519            final View buttonView, final BaseContainerView fromView,
520            final boolean animated, int animType, final Runnable onCompleteRunnable,
521            final PrivateTransitionCallbacks pCb) {
522        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
523        final Resources res = mLauncher.getResources();
524        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
525        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
526        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
527
528        final View toView = mLauncher.getWorkspace();
529        final View revealView = fromView.getRevealView();
530        final View contentView = fromView.getContentView();
531
532        final AnimationLayerSet layerViews = new AnimationLayerSet();
533
534        // If for some reason our views aren't initialized, don't animate
535        boolean initialized = buttonView != null;
536
537        // Cancel the current animation
538        cancelAnimation();
539
540        playCommonTransitionAnimations(toWorkspaceState,
541                animated, initialized, animation, layerViews);
542        if (!animated || !initialized) {
543            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
544                    fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
545                mAllAppsController.finishPullDown();
546            }
547            fromView.setVisibility(View.GONE);
548            pCb.onTransitionComplete();
549
550            // Run any queued runnables
551            if (onCompleteRunnable != null) {
552                onCompleteRunnable.run();
553            }
554            return;
555        }
556        if (animType == CIRCULAR_REVEAL) {
557            // hideAppsCustomizeHelper is called in some cases when it is already hidden
558            // don't perform all these no-op animations. In particularly, this was causing
559            // the all-apps button to pop in and out.
560            if (fromView.getVisibility() == View.VISIBLE) {
561                int width = revealView.getMeasuredWidth();
562                int height = revealView.getMeasuredHeight();
563                float revealRadius = (float) Math.hypot(width / 2, height / 2);
564                revealView.setVisibility(View.VISIBLE);
565                revealView.setAlpha(1f);
566                revealView.setTranslationY(0);
567                layerViews.addView(revealView);
568
569                // Calculate the final animation values
570                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
571                final float revealViewToXDrift = buttonViewToPanelDelta[0];
572                final float revealViewToYDrift = buttonViewToPanelDelta[1];
573
574                // The vertical motion of the apps panel should be delayed by one frame
575                // from the conceal animation in order to give the right feel. We correspondingly
576                // shorten the duration so that the slide and conceal end at the same time.
577                TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
578                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
579                        0, revealViewToYDrift);
580                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
581                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
582                panelDriftY.setInterpolator(decelerateInterpolator);
583                animation.play(panelDriftY);
584
585                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
586                        0, revealViewToXDrift);
587                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
588                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
589                panelDriftX.setInterpolator(decelerateInterpolator);
590                animation.play(panelDriftX);
591
592                // Setup animation for the reveal panel alpha
593                if (pCb.materialRevealViewFinalAlpha != 1f) {
594                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
595                            1f, pCb.materialRevealViewFinalAlpha);
596                    panelAlpha.setDuration(revealDuration);
597                    panelAlpha.setInterpolator(decelerateInterpolator);
598                    animation.play(panelAlpha);
599                }
600
601                // Setup the animation for the content view
602                layerViews.addView(contentView);
603
604                // Create the individual animators
605                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
606                        0, revealViewToYDrift);
607                contentView.setTranslationY(0);
608                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
609                pageDrift.setInterpolator(decelerateInterpolator);
610                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
611                animation.play(pageDrift);
612
613                contentView.setAlpha(1f);
614                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
615                itemsAlpha.setDuration(100);
616                itemsAlpha.setInterpolator(decelerateInterpolator);
617                animation.play(itemsAlpha);
618
619                // Invalidate the scrim throughout the animation to ensure the highlight
620                // cutout is correct throughout.
621                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
622                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
623                    @Override
624                    public void onAnimationUpdate(ValueAnimator animation) {
625                        mLauncher.getDragLayer().invalidateScrim();
626                    }
627                });
628                animation.play(invalidateScrim);
629
630                // Animate the all apps button
631                float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
632                AnimatorListenerAdapter listener =
633                        pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
634                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
635                        revealRadius, finalRadius).createRevealAnimator(revealView);
636                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
637                reveal.setDuration(revealDuration);
638                reveal.setStartDelay(itemsAlphaStagger);
639                if (listener != null) {
640                    reveal.addListener(listener);
641                }
642                animation.play(reveal);
643            }
644
645            animation.addListener(new AnimatorListenerAdapter() {
646                @Override
647                public void onAnimationEnd(Animator animation) {
648                    fromView.setVisibility(View.GONE);
649                    // Run any queued runnables
650                    if (onCompleteRunnable != null) {
651                        onCompleteRunnable.run();
652                    }
653
654                    // Reset page transforms
655                    if (contentView != null) {
656                        contentView.setTranslationX(0);
657                        contentView.setTranslationY(0);
658                        contentView.setAlpha(1);
659                    }
660
661                    // This can hold unnecessary references to views.
662                    cleanupAnimation();
663                    pCb.onTransitionComplete();
664                }
665            });
666
667            mCurrentAnimation = animation;
668            mCurrentAnimation.addListener(layerViews);
669            fromView.post(new StartAnimRunnable(animation, null));
670        } else if (animType == PULLUP) {
671            // We are animating the content view alpha, so ensure we have a layer for it
672            layerViews.addView(contentView);
673
674            animation.addListener(new AnimatorListenerAdapter() {
675                boolean canceled = false;
676                @Override
677                public void onAnimationCancel(Animator animation) {
678                    canceled = true;
679                }
680
681                @Override
682                public void onAnimationEnd(Animator animation) {
683                    if (canceled) return;
684                    // Run any queued runnables
685                    if (onCompleteRunnable != null) {
686                        onCompleteRunnable.run();
687                    }
688
689                    cleanupAnimation();
690                    pCb.onTransitionComplete();
691                }
692
693            });
694            boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
695
696            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
697            mCurrentAnimation = animation;
698            mCurrentAnimation.addListener(layerViews);
699            if (shouldPost) {
700                fromView.post(startAnimRunnable);
701            } else {
702                startAnimRunnable.run();
703            }
704        }
705        return;
706    }
707
708    /**
709     * Cancels the current animation.
710     */
711    private void cancelAnimation() {
712        if (mCurrentAnimation != null) {
713            mCurrentAnimation.setDuration(0);
714            mCurrentAnimation.cancel();
715            mCurrentAnimation = null;
716        }
717    }
718
719    @Thunk void cleanupAnimation() {
720        mCurrentAnimation = null;
721    }
722
723    private class StartAnimRunnable implements Runnable {
724
725        private final AnimatorSet mAnim;
726        private final View mViewToFocus;
727
728        public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
729            mAnim = anim;
730            mViewToFocus = viewToFocus;
731        }
732
733        @Override
734        public void run() {
735            if (mCurrentAnimation != mAnim) {
736                return;
737            }
738            if (mViewToFocus != null) {
739                mViewToFocus.requestFocus();
740            }
741            mAnim.start();
742        }
743    }
744}
745