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