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