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