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