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