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