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