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