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