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