LauncherStateTransitionAnimation.java revision b5e65c8bd3785409d4aeda21f2c88e75c9e22c9f
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.animation.ValueAnimator;
26import android.annotation.SuppressLint;
27import android.annotation.TargetApi;
28import android.content.res.Resources;
29import android.os.Build;
30import android.util.Log;
31import android.view.View;
32import android.view.animation.AccelerateInterpolator;
33import android.view.animation.DecelerateInterpolator;
34
35import com.android.launcher3.allapps.AllAppsContainerView;
36import com.android.launcher3.allapps.AllAppsTransitionController;
37import com.android.launcher3.anim.AnimationLayerSet;
38import com.android.launcher3.config.FeatureFlags;
39import com.android.launcher3.util.CircleRevealOutlineProvider;
40import com.android.launcher3.util.Thunk;
41import com.android.launcher3.widget.WidgetsContainerView;
42
43/**
44 * TODO: figure out what kind of tests we can write for this
45 *
46 * Things to test when changing the following class.
47 *   - Home from workspace
48 *          - from center screen
49 *          - from other screens
50 *   - Home from all apps
51 *          - from center screen
52 *          - from other screens
53 *   - Back from all apps
54 *          - from center screen
55 *          - from other screens
56 *   - Launch app from workspace and quit
57 *          - with back
58 *          - with home
59 *   - Launch app from all apps and quit
60 *          - with back
61 *          - with home
62 *   - Go to a screen that's not the default, then all
63 *     apps, and launch and app, and go back
64 *          - with back
65 *          -with home
66 *   - On workspace, long press power and go back
67 *          - with back
68 *          - with home
69 *   - On all apps, long press power and go back
70 *          - with back
71 *          - with home
72 *   - On workspace, power off
73 *   - On all apps, power off
74 *   - Launch an app and turn off the screen while in that app
75 *          - Go back with home key
76 *          - Go back with back key  TODO: make this not go to workspace
77 *          - From all apps
78 *          - From workspace
79 *   - Enter and exit car mode (becuase it causes an extra configuration changed)
80 *          - From all apps
81 *          - From the center workspace
82 *          - From another workspace
83 */
84public class LauncherStateTransitionAnimation {
85
86    /**
87     * animation used for all apps and widget tray when
88     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
89     */
90    public static final int CIRCULAR_REVEAL = 0;
91    /**
92     * animation used for all apps and not widget tray when
93     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
94     */
95    public static final int PULLUP = 1;
96
97    private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
98
99    /**
100     * Private callbacks made during transition setup.
101     */
102    private static class PrivateTransitionCallbacks {
103        private final float materialRevealViewFinalAlpha;
104
105        PrivateTransitionCallbacks(float revealAlpha) {
106            materialRevealViewFinalAlpha = revealAlpha;
107        }
108
109        float getMaterialRevealViewStartFinalRadius() {
110            return 0;
111        }
112        AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
113                View buttonView) {
114            return null;
115        }
116        void onTransitionComplete() {}
117    }
118
119    public static final String TAG = "LSTAnimation";
120
121    public static final int SINGLE_FRAME_DELAY = 16;
122
123    @Thunk Launcher mLauncher;
124    @Thunk AnimatorSet mCurrentAnimation;
125    AllAppsTransitionController mAllAppsController;
126
127    public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
128        mLauncher = l;
129        mAllAppsController = allAppsController;
130    }
131
132    /**
133     * Starts an animation to the apps view.
134     *
135     * @param startSearchAfterTransition Immediately starts app search after the transition to
136     *                                   All Apps is completed.
137     */
138    public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
139            final boolean animated, final boolean startSearchAfterTransition) {
140        final AllAppsContainerView toView = mLauncher.getAppsView();
141        final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
142        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
143            @Override
144            public float getMaterialRevealViewStartFinalRadius() {
145                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
146                return allAppsButtonSize / 2;
147            }
148            @Override
149            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
150                    final View revealView, final View allAppsButtonView) {
151                return new AnimatorListenerAdapter() {
152                    public void onAnimationStart(Animator animation) {
153                        allAppsButtonView.setVisibility(View.INVISIBLE);
154                    }
155                    public void onAnimationEnd(Animator animation) {
156                        allAppsButtonView.setVisibility(View.VISIBLE);
157                    }
158                };
159            }
160            @Override
161            void onTransitionComplete() {
162                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
163                if (startSearchAfterTransition) {
164                    toView.startAppsSearch();
165                }
166            }
167        };
168        int animType = CIRCULAR_REVEAL;
169        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
170            animType = PULLUP;
171        }
172        // Only animate the search bar if animating from spring loaded mode back to all apps
173        startAnimationToOverlay(fromWorkspaceState,
174                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
175    }
176
177    /**
178     * Starts an animation to the widgets view.
179     */
180    public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
181            final boolean animated) {
182        final WidgetsContainerView toView = mLauncher.getWidgetsView();
183        final View buttonView = mLauncher.getWidgetsButton();
184        startAnimationToOverlay(fromWorkspaceState,
185                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
186                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
187                    @Override
188                    void onTransitionComplete() {
189                        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
190                    }
191                });
192    }
193
194    /**
195     * Starts an animation to the workspace from the current overlay view.
196     */
197    public void startAnimationToWorkspace(final Launcher.State fromState,
198            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
199            final boolean animated, final Runnable onCompleteRunnable) {
200        if (toWorkspaceState != Workspace.State.NORMAL &&
201                toWorkspaceState != Workspace.State.SPRING_LOADED &&
202                toWorkspaceState != Workspace.State.OVERVIEW) {
203            Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
204        }
205
206        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
207                || mAllAppsController.isTransitioning()) {
208            int animType = CIRCULAR_REVEAL;
209            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
210                animType = PULLUP;
211            }
212            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
213                    animated, animType, onCompleteRunnable);
214        } else if (fromState == Launcher.State.WIDGETS ||
215                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
216            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
217                    animated, onCompleteRunnable);
218        } else {
219            startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
220                    animated, onCompleteRunnable);
221        }
222    }
223
224    /**
225     * Creates and starts a new animation to a particular overlay view.
226     */
227    @SuppressLint("NewApi")
228    private void startAnimationToOverlay(
229            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
230            final View buttonView, final BaseContainerView toView,
231            final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
232        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
233        final Resources res = mLauncher.getResources();
234        final boolean material = Utilities.ATLEAST_LOLLIPOP;
235        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
236        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
237
238        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
239
240        final View fromView = mLauncher.getWorkspace();
241
242        final AnimationLayerSet layerViews = new AnimationLayerSet();
243
244        // If for some reason our views aren't initialized, don't animate
245        boolean initialized = buttonView != null;
246
247        // Cancel the current animation
248        cancelAnimation();
249
250        final View contentView = toView.getContentView();
251        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
252                animated, initialized, animation, layerViews);
253        if (!animated || !initialized) {
254            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
255                    toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
256                mAllAppsController.finishPullUp();
257            }
258            toView.setTranslationX(0.0f);
259            toView.setTranslationY(0.0f);
260            toView.setScaleX(1.0f);
261            toView.setScaleY(1.0f);
262            toView.setAlpha(1.0f);
263            toView.setVisibility(View.VISIBLE);
264
265            // Show the content view
266            contentView.setVisibility(View.VISIBLE);
267
268            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
269            dispatchOnLauncherTransitionStart(fromView, animated, false);
270            dispatchOnLauncherTransitionEnd(fromView, animated, false);
271            dispatchOnLauncherTransitionPrepare(toView, animated, false);
272            dispatchOnLauncherTransitionStart(toView, animated, false);
273            dispatchOnLauncherTransitionEnd(toView, animated, false);
274            pCb.onTransitionComplete();
275            return;
276        }
277        if (animType == CIRCULAR_REVEAL) {
278            // Setup the reveal view animation
279            final View revealView = toView.getRevealView();
280
281            int width = revealView.getMeasuredWidth();
282            int height = revealView.getMeasuredHeight();
283            float revealRadius = (float) Math.hypot(width / 2, height / 2);
284            revealView.setVisibility(View.VISIBLE);
285            revealView.setAlpha(0f);
286            revealView.setTranslationY(0f);
287            revealView.setTranslationX(0f);
288
289            // Calculate the final animation values
290            final float revealViewToAlpha;
291            final float revealViewToXDrift;
292            final float revealViewToYDrift;
293            if (material) {
294                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
295                        revealView, buttonView, null);
296                revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
297                revealViewToYDrift = buttonViewToPanelDelta[1];
298                revealViewToXDrift = buttonViewToPanelDelta[0];
299            } else {
300                revealViewToAlpha = 0f;
301                revealViewToYDrift = 2 * height / 3;
302                revealViewToXDrift = 0;
303            }
304
305            // Create the animators
306            PropertyValuesHolder panelAlpha =
307                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
308            PropertyValuesHolder panelDriftY =
309                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
310            PropertyValuesHolder panelDriftX =
311                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
312            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
313                    panelAlpha, panelDriftY, panelDriftX);
314            panelAlphaAndDrift.setDuration(revealDuration);
315            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
316
317            // Play the animation
318            layerViews.addView(revealView);
319            animation.play(panelAlphaAndDrift);
320
321            // Setup the animation for the content view
322            contentView.setVisibility(View.VISIBLE);
323            contentView.setAlpha(0f);
324            contentView.setTranslationY(revealViewToYDrift);
325            layerViews.addView(contentView);
326
327            // Create the individual animators
328            ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
329                    revealViewToYDrift, 0);
330            pageDrift.setDuration(revealDuration);
331            pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
332            pageDrift.setStartDelay(itemsAlphaStagger);
333            animation.play(pageDrift);
334
335            ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
336            itemsAlpha.setDuration(revealDuration);
337            itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
338            itemsAlpha.setStartDelay(itemsAlphaStagger);
339            animation.play(itemsAlpha);
340
341            if (material) {
342                float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
343                AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
344                        revealView, buttonView);
345                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
346                        startRadius, revealRadius).createRevealAnimator(revealView);
347                reveal.setDuration(revealDuration);
348                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
349                if (listener != null) {
350                    reveal.addListener(listener);
351                }
352                animation.play(reveal);
353            }
354
355            animation.addListener(new AnimatorListenerAdapter() {
356                @Override
357                public void onAnimationEnd(Animator animation) {
358                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
359                    dispatchOnLauncherTransitionEnd(toView, animated, false);
360
361                    // Hide the reveal view
362                    revealView.setVisibility(View.INVISIBLE);
363
364                    // This can hold unnecessary references to views.
365                    cleanupAnimation();
366                    pCb.onTransitionComplete();
367                }
368
369            });
370
371            // Dispatch the prepare transition signal
372            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
373            dispatchOnLauncherTransitionPrepare(toView, animated, false);
374
375            final AnimatorSet stateAnimation = animation;
376            final Runnable startAnimRunnable = new Runnable() {
377                public void run() {
378                    // Check that mCurrentAnimation hasn't changed while
379                    // we waited for a layout/draw pass
380                    if (mCurrentAnimation != stateAnimation)
381                        return;
382                    dispatchOnLauncherTransitionStart(fromView, animated, false);
383                    dispatchOnLauncherTransitionStart(toView, animated, false);
384
385                    // Focus the new view
386                    toView.requestFocus();
387
388                    stateAnimation.start();
389                }
390            };
391            toView.bringToFront();
392            toView.setVisibility(View.VISIBLE);
393
394            animation.addListener(layerViews);
395            toView.post(startAnimRunnable);
396            mCurrentAnimation = animation;
397        } else if (animType == PULLUP) {
398            // We are animating the content view alpha, so ensure we have a layer for it
399            layerViews.addView(contentView);
400
401            animation.addListener(new AnimatorListenerAdapter() {
402                @Override
403                public void onAnimationEnd(Animator animation) {
404                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
405                    dispatchOnLauncherTransitionEnd(toView, animated, false);
406                    cleanupAnimation();
407                    pCb.onTransitionComplete();
408                }
409            });
410            boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
411
412            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
413            dispatchOnLauncherTransitionPrepare(toView, animated, false);
414
415            final AnimatorSet stateAnimation = animation;
416            final Runnable startAnimRunnable = new Runnable() {
417                public void run() {
418                    // Check that mCurrentAnimation hasn't changed while
419                    // we waited for a layout/draw pass
420                    if (mCurrentAnimation != stateAnimation)
421                        return;
422
423                    dispatchOnLauncherTransitionStart(fromView, animated, false);
424                    dispatchOnLauncherTransitionStart(toView, animated, false);
425
426                    toView.requestFocus();
427                    stateAnimation.start();
428                }
429            };
430            mCurrentAnimation = animation;
431            mCurrentAnimation.addListener(layerViews);
432            if (shouldPost) {
433                toView.post(startAnimRunnable);
434            } else {
435                startAnimRunnable.run();
436            }
437        }
438    }
439
440    /**
441     * Plays animations used by various transitions.
442     */
443    private void playCommonTransitionAnimations(
444            Workspace.State toWorkspaceState, View fromView, View toView,
445            boolean animated, boolean initialized, AnimatorSet animation,
446            AnimationLayerSet layerViews) {
447        // Create the workspace animation.
448        // NOTE: this call apparently also sets the state for the workspace if !animated
449        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
450                animated, layerViews);
451
452        if (animated && initialized) {
453            // Play the workspace animation
454            if (workspaceAnim != null) {
455                animation.play(workspaceAnim);
456            }
457            // Dispatch onLauncherTransitionStep() as the animation interpolates.
458            animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
459        }
460    }
461
462    /**
463     * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
464     * {@param fromView} and {@param toView} as the animation interpolates.
465     *
466     * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
467     */
468    private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
469        ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
470        updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
471            @Override
472            public void onAnimationUpdate(ValueAnimator animation) {
473                dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
474                dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
475            }
476        });
477        return updateAnimator;
478    }
479
480    /**
481     * Starts an animation to the workspace from the apps view.
482     */
483    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
484            final Workspace.State toWorkspaceState, final boolean animated, int type,
485            final Runnable onCompleteRunnable) {
486        AllAppsContainerView appsView = mLauncher.getAppsView();
487        // No alpha anim from all apps
488        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
489            @Override
490            float getMaterialRevealViewStartFinalRadius() {
491                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
492                return allAppsButtonSize / 2;
493            }
494            @Override
495            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
496                    final View revealView, final View allAppsButtonView) {
497                return new AnimatorListenerAdapter() {
498                    public void onAnimationStart(Animator animation) {
499                        // We set the alpha instead of visibility to ensure that the focus does not
500                        // get taken from the all apps view
501                        allAppsButtonView.setVisibility(View.VISIBLE);
502                        allAppsButtonView.setAlpha(0f);
503                    }
504                    public void onAnimationEnd(Animator animation) {
505                        // Hide the reveal view
506                        revealView.setVisibility(View.INVISIBLE);
507
508                        // Show the all apps button, and focus it
509                        allAppsButtonView.setAlpha(1f);
510                    }
511                };
512            }
513            @Override
514            void onTransitionComplete() {
515                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
516            }
517        };
518        // Only animate the search bar if animating to spring loaded mode from all apps
519        startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
520                mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
521                animated, type, onCompleteRunnable, cb);
522    }
523
524    /**
525     * Starts an animation to the workspace from the widgets view.
526     */
527    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
528            final Workspace.State toWorkspaceState, final boolean animated,
529            final Runnable onCompleteRunnable) {
530        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
531        PrivateTransitionCallbacks cb =
532                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
533            @Override
534            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
535                    final View revealView, final View widgetsButtonView) {
536                return new AnimatorListenerAdapter() {
537                    public void onAnimationEnd(Animator animation) {
538                        // Hide the reveal view
539                        revealView.setVisibility(View.INVISIBLE);
540                    }
541                };
542            }
543            @Override
544            void onTransitionComplete() {
545                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
546            }
547        };
548        startAnimationToWorkspaceFromOverlay(
549                fromWorkspaceState, toWorkspaceState,
550                mLauncher.getWidgetsButton(), widgetsView,
551                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
552    }
553
554    /**
555     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
556     */
557    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
558            final Workspace.State toWorkspaceState, final boolean animated,
559            final Runnable onCompleteRunnable) {
560        final View fromWorkspace = mLauncher.getWorkspace();
561        final AnimationLayerSet layerViews = new AnimationLayerSet();
562        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
563
564        // Cancel the current animation
565        cancelAnimation();
566
567        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
568
569        playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
570                animated, animated, animation, layerViews);
571
572        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
573
574        if (animated) {
575            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
576
577            final AnimatorSet stateAnimation = animation;
578            final Runnable startAnimRunnable = new Runnable() {
579                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
580                public void run() {
581                    // Check that mCurrentAnimation hasn't changed while
582                    // we waited for a layout/draw pass
583                    if (mCurrentAnimation != stateAnimation)
584                        return;
585
586                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
587                    stateAnimation.start();
588                }
589            };
590            animation.addListener(new AnimatorListenerAdapter() {
591                @Override
592                public void onAnimationEnd(Animator animation) {
593                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
594
595                    // Run any queued runnables
596                    if (onCompleteRunnable != null) {
597                        onCompleteRunnable.run();
598                    }
599
600                    // This can hold unnecessary references to views.
601                    cleanupAnimation();
602                }
603            });
604            stateAnimation.addListener(layerViews);
605            fromWorkspace.post(startAnimRunnable);
606            mCurrentAnimation = animation;
607        } else /* if (!animated) */ {
608            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
609            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
610            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
611
612            // Run any queued runnables
613            if (onCompleteRunnable != null) {
614                onCompleteRunnable.run();
615            }
616
617            mCurrentAnimation = null;
618        }
619    }
620
621    /**
622     * Creates and starts a new animation to the workspace.
623     */
624    private void startAnimationToWorkspaceFromOverlay(
625            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
626            final View buttonView, final BaseContainerView fromView,
627            final boolean animated, int animType, final Runnable onCompleteRunnable,
628            final PrivateTransitionCallbacks pCb) {
629        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
630        final Resources res = mLauncher.getResources();
631        final boolean material = Utilities.ATLEAST_LOLLIPOP;
632        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
633        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
634        final int itemsAlphaStagger =
635                res.getInteger(R.integer.config_overlayItemsAlphaStagger);
636
637        final View toView = mLauncher.getWorkspace();
638        final View revealView = fromView.getRevealView();
639        final View contentView = fromView.getContentView();
640
641        final AnimationLayerSet layerViews = new AnimationLayerSet();
642
643        // If for some reason our views aren't initialized, don't animate
644        boolean initialized = buttonView != null;
645
646        // Cancel the current animation
647        cancelAnimation();
648
649        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
650
651        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
652                animated, initialized, animation, layerViews);
653        if (!animated || !initialized) {
654            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
655                    fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
656                mAllAppsController.finishPullDown();
657            }
658            fromView.setVisibility(View.GONE);
659            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
660            dispatchOnLauncherTransitionStart(fromView, animated, true);
661            dispatchOnLauncherTransitionEnd(fromView, animated, true);
662            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
663            dispatchOnLauncherTransitionStart(toView, animated, true);
664            dispatchOnLauncherTransitionEnd(toView, animated, true);
665            pCb.onTransitionComplete();
666
667            // Run any queued runnables
668            if (onCompleteRunnable != null) {
669                onCompleteRunnable.run();
670            }
671            return;
672        }
673        if (animType == CIRCULAR_REVEAL) {
674            // hideAppsCustomizeHelper is called in some cases when it is already hidden
675            // don't perform all these no-op animations. In particularly, this was causing
676            // the all-apps button to pop in and out.
677            if (fromView.getVisibility() == View.VISIBLE) {
678                int width = revealView.getMeasuredWidth();
679                int height = revealView.getMeasuredHeight();
680                float revealRadius = (float) Math.hypot(width / 2, height / 2);
681                revealView.setVisibility(View.VISIBLE);
682                revealView.setAlpha(1f);
683                revealView.setTranslationY(0);
684                layerViews.addView(revealView);
685
686                // Calculate the final animation values
687                final float revealViewToXDrift;
688                final float revealViewToYDrift;
689                if (material) {
690                    int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
691                            buttonView, null);
692                    revealViewToYDrift = buttonViewToPanelDelta[1];
693                    revealViewToXDrift = buttonViewToPanelDelta[0];
694                } else {
695                    revealViewToYDrift = 2 * height / 3;
696                    revealViewToXDrift = 0;
697                }
698
699                // The vertical motion of the apps panel should be delayed by one frame
700                // from the conceal animation in order to give the right feel. We correspondingly
701                // shorten the duration so that the slide and conceal end at the same time.
702                TimeInterpolator decelerateInterpolator = material ?
703                        new LogDecelerateInterpolator(100, 0) :
704                        new DecelerateInterpolator(1f);
705                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
706                        0, revealViewToYDrift);
707                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
708                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
709                panelDriftY.setInterpolator(decelerateInterpolator);
710                animation.play(panelDriftY);
711
712                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
713                        0, revealViewToXDrift);
714                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
715                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
716                panelDriftX.setInterpolator(decelerateInterpolator);
717                animation.play(panelDriftX);
718
719                // Setup animation for the reveal panel alpha
720                final float revealViewToAlpha = !material ? 0f :
721                        pCb.materialRevealViewFinalAlpha;
722                if (revealViewToAlpha != 1f) {
723                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
724                            1f, revealViewToAlpha);
725                    panelAlpha.setDuration(material ? revealDuration : 150);
726                    panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
727                    panelAlpha.setInterpolator(decelerateInterpolator);
728                    animation.play(panelAlpha);
729                }
730
731                // Setup the animation for the content view
732                layerViews.addView(contentView);
733
734                // Create the individual animators
735                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
736                        0, revealViewToYDrift);
737                contentView.setTranslationY(0);
738                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
739                pageDrift.setInterpolator(decelerateInterpolator);
740                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
741                animation.play(pageDrift);
742
743                contentView.setAlpha(1f);
744                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
745                itemsAlpha.setDuration(100);
746                itemsAlpha.setInterpolator(decelerateInterpolator);
747                animation.play(itemsAlpha);
748
749                // Invalidate the scrim throughout the animation to ensure the highlight
750                // cutout is correct throughout.
751                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
752                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
753                    @Override
754                    public void onAnimationUpdate(ValueAnimator animation) {
755                        mLauncher.getDragLayer().invalidateScrim();
756                    }
757                });
758                animation.play(invalidateScrim);
759
760                if (material) {
761                    // Animate the all apps button
762                    float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
763                    AnimatorListenerAdapter listener =
764                            pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
765                    Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
766                            revealRadius, finalRadius).createRevealAnimator(revealView);
767                    reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
768                    reveal.setDuration(revealDuration);
769                    reveal.setStartDelay(itemsAlphaStagger);
770                    if (listener != null) {
771                        reveal.addListener(listener);
772                    }
773                    animation.play(reveal);
774                }
775            }
776
777            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
778            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
779
780            animation.addListener(new AnimatorListenerAdapter() {
781                @Override
782                public void onAnimationEnd(Animator animation) {
783                    fromView.setVisibility(View.GONE);
784                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
785                    dispatchOnLauncherTransitionEnd(toView, animated, true);
786
787                    // Run any queued runnables
788                    if (onCompleteRunnable != null) {
789                        onCompleteRunnable.run();
790                    }
791
792                    // Reset page transforms
793                    if (contentView != null) {
794                        contentView.setTranslationX(0);
795                        contentView.setTranslationY(0);
796                        contentView.setAlpha(1);
797                    }
798
799                    // This can hold unnecessary references to views.
800                    cleanupAnimation();
801                    pCb.onTransitionComplete();
802                }
803            });
804
805            final AnimatorSet stateAnimation = animation;
806            final Runnable startAnimRunnable = new Runnable() {
807                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
808                public void run() {
809                    // Check that mCurrentAnimation hasn't changed while
810                    // we waited for a layout/draw pass
811                    if (mCurrentAnimation != stateAnimation)
812                        return;
813
814                    dispatchOnLauncherTransitionStart(fromView, animated, false);
815                    dispatchOnLauncherTransitionStart(toView, animated, false);
816                    stateAnimation.start();
817                }
818            };
819            mCurrentAnimation = animation;
820            mCurrentAnimation.addListener(layerViews);
821            fromView.post(startAnimRunnable);
822        } else if (animType == PULLUP) {
823            // We are animating the content view alpha, so ensure we have a layer for it
824            layerViews.addView(contentView);
825
826            animation.addListener(new AnimatorListenerAdapter() {
827                boolean canceled = false;
828                @Override
829                public void onAnimationCancel(Animator animation) {
830                    canceled = true;
831                }
832
833                @Override
834                public void onAnimationEnd(Animator animation) {
835                    if (canceled) return;
836                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
837                    dispatchOnLauncherTransitionEnd(toView, animated, true);
838                    // Run any queued runnables
839                    if (onCompleteRunnable != null) {
840                        onCompleteRunnable.run();
841                    }
842
843                    cleanupAnimation();
844                    pCb.onTransitionComplete();
845                }
846
847            });
848            boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
849
850            // Dispatch the prepare transition signal
851            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
852            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
853
854            final AnimatorSet stateAnimation = animation;
855            final Runnable startAnimRunnable = new Runnable() {
856                public void run() {
857                    // Check that mCurrentAnimation hasn't changed while
858                    // we waited for a layout/draw pass
859                    if (mCurrentAnimation != stateAnimation)
860                        return;
861
862                    dispatchOnLauncherTransitionStart(fromView, animated, false);
863                    dispatchOnLauncherTransitionStart(toView, animated, false);
864
865                    // Focus the new view
866                    toView.requestFocus();
867                    stateAnimation.start();
868                }
869            };
870            mCurrentAnimation = animation;
871            mCurrentAnimation.addListener(layerViews);
872            if (shouldPost) {
873                fromView.post(startAnimRunnable);
874            } else {
875                startAnimRunnable.run();
876            }
877        }
878        return;
879    }
880
881    /**
882     * Dispatches the prepare-transition event to suitable views.
883     */
884    void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
885            boolean multiplePagesVisible) {
886        if (v instanceof LauncherTransitionable) {
887            ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
888                    multiplePagesVisible);
889        }
890    }
891
892    /**
893     * Dispatches the start-transition event to suitable views.
894     */
895    void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
896        if (v instanceof LauncherTransitionable) {
897            ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
898                    toWorkspace);
899        }
900
901        // Update the workspace transition step as well
902        dispatchOnLauncherTransitionStep(v, 0f);
903    }
904
905    /**
906     * Dispatches the step-transition event to suitable views.
907     */
908    void dispatchOnLauncherTransitionStep(View v, float t) {
909        if (v instanceof LauncherTransitionable) {
910            ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
911        }
912    }
913
914    /**
915     * Dispatches the end-transition event to suitable views.
916     */
917    void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
918        if (v instanceof LauncherTransitionable) {
919            ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
920                    toWorkspace);
921        }
922
923        // Update the workspace transition step as well
924        dispatchOnLauncherTransitionStep(v, 1f);
925    }
926
927    /**
928     * Cancels the current animation.
929     */
930    private void cancelAnimation() {
931        if (mCurrentAnimation != null) {
932            mCurrentAnimation.setDuration(0);
933            mCurrentAnimation.cancel();
934            mCurrentAnimation = null;
935        }
936    }
937
938    @Thunk void cleanupAnimation() {
939        mCurrentAnimation = null;
940    }
941}
942