LauncherStateTransitionAnimation.java revision d7d740287f104c0597f01b66399c7e65a9b57dab
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        mCurrentAnimation = 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        mCurrentAnimation = 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 AnimatorSet 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
280            return null;
281        }
282        if (animType == CIRCULAR_REVEAL) {
283            // Setup the reveal view animation
284            final View revealView = toView.getRevealView();
285
286            int width = revealView.getMeasuredWidth();
287            int height = revealView.getMeasuredHeight();
288            float revealRadius = (float) Math.hypot(width / 2, height / 2);
289            revealView.setVisibility(View.VISIBLE);
290            revealView.setAlpha(0f);
291            revealView.setTranslationY(0f);
292            revealView.setTranslationX(0f);
293
294            // Calculate the final animation values
295            final float revealViewToAlpha;
296            final float revealViewToXDrift;
297            final float revealViewToYDrift;
298            if (material) {
299                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
300                        revealView, buttonView, null);
301                revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
302                revealViewToYDrift = buttonViewToPanelDelta[1];
303                revealViewToXDrift = buttonViewToPanelDelta[0];
304            } else {
305                revealViewToAlpha = 0f;
306                revealViewToYDrift = 2 * height / 3;
307                revealViewToXDrift = 0;
308            }
309
310            // Create the animators
311            PropertyValuesHolder panelAlpha =
312                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
313            PropertyValuesHolder panelDriftY =
314                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
315            PropertyValuesHolder panelDriftX =
316                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
317            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
318                    panelAlpha, panelDriftY, panelDriftX);
319            panelAlphaAndDrift.setDuration(revealDuration);
320            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
321
322            // Play the animation
323            layerViews.put(revealView, BUILD_AND_SET_LAYER);
324            animation.play(panelAlphaAndDrift);
325
326            // Setup the animation for the content view
327            contentView.setVisibility(View.VISIBLE);
328            contentView.setAlpha(0f);
329            contentView.setTranslationY(revealViewToYDrift);
330            layerViews.put(contentView, BUILD_AND_SET_LAYER);
331
332            // Create the individual animators
333            ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
334                    revealViewToYDrift, 0);
335            pageDrift.setDuration(revealDuration);
336            pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
337            pageDrift.setStartDelay(itemsAlphaStagger);
338            animation.play(pageDrift);
339
340            ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
341            itemsAlpha.setDuration(revealDuration);
342            itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
343            itemsAlpha.setStartDelay(itemsAlphaStagger);
344            animation.play(itemsAlpha);
345
346            if (material) {
347                float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
348                AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
349                        revealView, buttonView);
350                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
351                        startRadius, revealRadius).createRevealAnimator(revealView);
352                reveal.setDuration(revealDuration);
353                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
354                if (listener != null) {
355                    reveal.addListener(listener);
356                }
357                animation.play(reveal);
358            }
359
360            animation.addListener(new AnimatorListenerAdapter() {
361                @Override
362                public void onAnimationEnd(Animator animation) {
363                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
364                    dispatchOnLauncherTransitionEnd(toView, animated, false);
365
366                    // Hide the reveal view
367                    revealView.setVisibility(View.INVISIBLE);
368
369                    // Disable all necessary layers
370                    for (View v : layerViews.keySet()) {
371                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
372                            v.setLayerType(View.LAYER_TYPE_NONE, null);
373                        }
374                    }
375
376                    // This can hold unnecessary references to views.
377                    cleanupAnimation();
378                    pCb.onTransitionComplete();
379                }
380
381            });
382
383            // Dispatch the prepare transition signal
384            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
385            dispatchOnLauncherTransitionPrepare(toView, animated, false);
386
387            final AnimatorSet stateAnimation = animation;
388            final Runnable startAnimRunnable = new Runnable() {
389                public void run() {
390                    // Check that mCurrentAnimation hasn't changed while
391                    // we waited for a layout/draw pass
392                    if (mCurrentAnimation != stateAnimation)
393                        return;
394                    dispatchOnLauncherTransitionStart(fromView, animated, false);
395                    dispatchOnLauncherTransitionStart(toView, animated, false);
396
397                    // Enable all necessary layers
398                    for (View v : layerViews.keySet()) {
399                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
400                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
401                        }
402                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
403                            v.buildLayer();
404                        }
405                    }
406
407                    // Focus the new view
408                    toView.requestFocus();
409
410                    stateAnimation.start();
411                }
412            };
413            toView.bringToFront();
414            toView.setVisibility(View.VISIBLE);
415            toView.post(startAnimRunnable);
416
417            return animation;
418        } else if (animType == PULLUP) {
419            // We are animating the content view alpha, so ensure we have a layer for it
420            layerViews.put(contentView, BUILD_AND_SET_LAYER);
421
422            animation.addListener(new AnimatorListenerAdapter() {
423                  @Override
424                  public void onAnimationEnd(Animator animation) {
425                      dispatchOnLauncherTransitionEnd(fromView, animated, false);
426                      dispatchOnLauncherTransitionEnd(toView, animated, false);
427
428                      // Disable all necessary layers
429                      for (View v : layerViews.keySet()) {
430                          if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
431                              v.setLayerType(View.LAYER_TYPE_NONE, null);
432                          }
433                      }
434
435                      cleanupAnimation();
436                      pCb.onTransitionComplete();
437                  }
438            });
439            mAllAppsController.animateToAllApps(animation, revealDurationSlide);
440
441            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
442            dispatchOnLauncherTransitionPrepare(toView, animated, false);
443
444            final AnimatorSet stateAnimation = animation;
445            final Runnable startAnimRunnable = new Runnable() {
446                public void run() {
447                    // Check that mCurrentAnimation hasn't changed while
448                    // we waited for a layout/draw pass
449                    if (mCurrentAnimation != stateAnimation)
450                        return;
451
452                    dispatchOnLauncherTransitionStart(fromView, animated, false);
453                    dispatchOnLauncherTransitionStart(toView, animated, false);
454
455                    // Enable all necessary layers
456                    for (View v : layerViews.keySet()) {
457                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
458                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
459                        }
460                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
461                            v.buildLayer();
462                        }
463                    }
464
465                    toView.requestFocus();
466                    stateAnimation.start();
467                }
468            };
469            toView.post(startAnimRunnable);
470            return animation;
471        }
472        return null;
473    }
474
475    /**
476     * Plays animations used by various transitions.
477     */
478    private void playCommonTransitionAnimations(
479            Workspace.State toWorkspaceState, View fromView, View toView,
480            boolean animated, boolean initialized, AnimatorSet animation,
481            HashMap<View, Integer> layerViews) {
482        // Create the workspace animation.
483        // NOTE: this call apparently also sets the state for the workspace if !animated
484        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
485                animated, layerViews);
486
487        if (animated && initialized) {
488            // Play the workspace animation
489            if (workspaceAnim != null) {
490                animation.play(workspaceAnim);
491            }
492            // Dispatch onLauncherTransitionStep() as the animation interpolates.
493            animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
494        }
495    }
496
497    /**
498     * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
499     * {@param fromView} and {@param toView} as the animation interpolates.
500     *
501     * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
502     */
503    private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
504        ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
505        updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
506            @Override
507            public void onAnimationUpdate(ValueAnimator animation) {
508                dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
509                dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
510            }
511        });
512        return updateAnimator;
513    }
514
515    /**
516     * Starts an animation to the workspace from the apps view.
517     */
518    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
519            final Workspace.State toWorkspaceState, final boolean animated, int type,
520            final Runnable onCompleteRunnable) {
521        AllAppsContainerView appsView = mLauncher.getAppsView();
522        // No alpha anim from all apps
523        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
524            @Override
525            float getMaterialRevealViewStartFinalRadius() {
526                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
527                return allAppsButtonSize / 2;
528            }
529            @Override
530            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
531                    final View revealView, final View allAppsButtonView) {
532                return new AnimatorListenerAdapter() {
533                    public void onAnimationStart(Animator animation) {
534                        // We set the alpha instead of visibility to ensure that the focus does not
535                        // get taken from the all apps view
536                        allAppsButtonView.setVisibility(View.VISIBLE);
537                        allAppsButtonView.setAlpha(0f);
538                    }
539                    public void onAnimationEnd(Animator animation) {
540                        // Hide the reveal view
541                        revealView.setVisibility(View.INVISIBLE);
542
543                        // Show the all apps button, and focus it
544                        allAppsButtonView.setAlpha(1f);
545                    }
546                };
547            }
548            @Override
549            void onTransitionComplete() {
550                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
551            }
552        };
553        // Only animate the search bar if animating to spring loaded mode from all apps
554        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
555                mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
556                animated, type, onCompleteRunnable, cb);
557    }
558
559    /**
560     * Starts an animation to the workspace from the widgets view.
561     */
562    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
563            final Workspace.State toWorkspaceState, final boolean animated,
564            final Runnable onCompleteRunnable) {
565        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
566        PrivateTransitionCallbacks cb =
567                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
568            @Override
569            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
570                    final View revealView, final View widgetsButtonView) {
571                return new AnimatorListenerAdapter() {
572                    public void onAnimationEnd(Animator animation) {
573                        // Hide the reveal view
574                        revealView.setVisibility(View.INVISIBLE);
575                    }
576                };
577            }
578            @Override
579            void onTransitionComplete() {
580                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
581            }
582        };
583        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(
584                fromWorkspaceState, toWorkspaceState,
585                mLauncher.getWidgetsButton(), widgetsView,
586                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
587    }
588
589    /**
590     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
591     */
592    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
593            final Workspace.State toWorkspaceState, final boolean animated,
594            final Runnable onCompleteRunnable) {
595        final View fromWorkspace = mLauncher.getWorkspace();
596        final HashMap<View, Integer> layerViews = new HashMap<>();
597        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
598        final int revealDuration = mLauncher.getResources()
599                .getInteger(R.integer.config_overlayRevealTime);
600
601        // Cancel the current animation
602        cancelAnimation();
603
604        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
605
606        playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
607                animated, animated, animation, layerViews);
608
609        if (animated) {
610            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
611
612            final AnimatorSet stateAnimation = animation;
613            final Runnable startAnimRunnable = new Runnable() {
614                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
615                public void run() {
616                    // Check that mCurrentAnimation hasn't changed while
617                    // we waited for a layout/draw pass
618                    if (mCurrentAnimation != stateAnimation)
619                        return;
620
621                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
622
623                    // Enable all necessary layers
624                    for (View v : layerViews.keySet()) {
625                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
626                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
627                        }
628                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
629                            v.buildLayer();
630                        }
631                    }
632                    stateAnimation.start();
633                }
634            };
635            animation.addListener(new AnimatorListenerAdapter() {
636                @Override
637                public void onAnimationEnd(Animator animation) {
638                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
639
640                    // Run any queued runnables
641                    if (onCompleteRunnable != null) {
642                        onCompleteRunnable.run();
643                    }
644
645                    // Disable all necessary layers
646                    for (View v : layerViews.keySet()) {
647                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
648                            v.setLayerType(View.LAYER_TYPE_NONE, null);
649                        }
650                    }
651
652                    // This can hold unnecessary references to views.
653                    cleanupAnimation();
654                }
655            });
656            fromWorkspace.post(startAnimRunnable);
657            mCurrentAnimation = animation;
658        } else /* if (!animated) */ {
659            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
660            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
661            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
662
663            // Run any queued runnables
664            if (onCompleteRunnable != null) {
665                onCompleteRunnable.run();
666            }
667
668            mCurrentAnimation = null;
669        }
670    }
671
672    /**
673     * Creates and starts a new animation to the workspace.
674     */
675    private AnimatorSet startAnimationToWorkspaceFromOverlay(
676            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
677            final View buttonView, final BaseContainerView fromView,
678            final boolean animated, int animType, final Runnable onCompleteRunnable,
679            final PrivateTransitionCallbacks pCb) {
680        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
681        final Resources res = mLauncher.getResources();
682        final boolean material = Utilities.ATLEAST_LOLLIPOP;
683        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
684        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
685        final int itemsAlphaStagger =
686                res.getInteger(R.integer.config_overlayItemsAlphaStagger);
687
688        final View toView = mLauncher.getWorkspace();
689        final View revealView = fromView.getRevealView();
690        final View contentView = fromView.getContentView();
691
692        final HashMap<View, Integer> layerViews = new HashMap<>();
693
694        // If for some reason our views aren't initialized, don't animate
695        boolean initialized = buttonView != null;
696
697        // Cancel the current animation
698        cancelAnimation();
699
700        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
701
702        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
703                animated, initialized, animation, layerViews);
704        if (!animated || !initialized) {
705            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
706                    fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
707                mAllAppsController.finishPullDown();
708            }
709            fromView.setVisibility(View.GONE);
710            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
711            dispatchOnLauncherTransitionStart(fromView, animated, true);
712            dispatchOnLauncherTransitionEnd(fromView, animated, true);
713            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
714            dispatchOnLauncherTransitionStart(toView, animated, true);
715            dispatchOnLauncherTransitionEnd(toView, animated, true);
716            pCb.onTransitionComplete();
717
718            // Run any queued runnables
719            if (onCompleteRunnable != null) {
720                onCompleteRunnable.run();
721            }
722            return null;
723        }
724        if (animType == CIRCULAR_REVEAL) {
725            // hideAppsCustomizeHelper is called in some cases when it is already hidden
726            // don't perform all these no-op animations. In particularly, this was causing
727            // the all-apps button to pop in and out.
728            if (fromView.getVisibility() == View.VISIBLE) {
729                int width = revealView.getMeasuredWidth();
730                int height = revealView.getMeasuredHeight();
731                float revealRadius = (float) Math.hypot(width / 2, height / 2);
732                revealView.setVisibility(View.VISIBLE);
733                revealView.setAlpha(1f);
734                revealView.setTranslationY(0);
735                layerViews.put(revealView, BUILD_AND_SET_LAYER);
736
737                // Calculate the final animation values
738                final float revealViewToXDrift;
739                final float revealViewToYDrift;
740                if (material) {
741                    int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
742                            buttonView, null);
743                    revealViewToYDrift = buttonViewToPanelDelta[1];
744                    revealViewToXDrift = buttonViewToPanelDelta[0];
745                } else {
746                    revealViewToYDrift = 2 * height / 3;
747                    revealViewToXDrift = 0;
748                }
749
750                // The vertical motion of the apps panel should be delayed by one frame
751                // from the conceal animation in order to give the right feel. We correspondingly
752                // shorten the duration so that the slide and conceal end at the same time.
753                TimeInterpolator decelerateInterpolator = material ?
754                        new LogDecelerateInterpolator(100, 0) :
755                        new DecelerateInterpolator(1f);
756                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
757                        0, revealViewToYDrift);
758                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
759                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
760                panelDriftY.setInterpolator(decelerateInterpolator);
761                animation.play(panelDriftY);
762
763                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
764                        0, revealViewToXDrift);
765                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
766                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
767                panelDriftX.setInterpolator(decelerateInterpolator);
768                animation.play(panelDriftX);
769
770                // Setup animation for the reveal panel alpha
771                final float revealViewToAlpha = !material ? 0f :
772                        pCb.materialRevealViewFinalAlpha;
773                if (revealViewToAlpha != 1f) {
774                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
775                            1f, revealViewToAlpha);
776                    panelAlpha.setDuration(material ? revealDuration : 150);
777                    panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
778                    panelAlpha.setInterpolator(decelerateInterpolator);
779                    animation.play(panelAlpha);
780                }
781
782                // Setup the animation for the content view
783                layerViews.put(contentView, BUILD_AND_SET_LAYER);
784
785                // Create the individual animators
786                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
787                        0, revealViewToYDrift);
788                contentView.setTranslationY(0);
789                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
790                pageDrift.setInterpolator(decelerateInterpolator);
791                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
792                animation.play(pageDrift);
793
794                contentView.setAlpha(1f);
795                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
796                itemsAlpha.setDuration(100);
797                itemsAlpha.setInterpolator(decelerateInterpolator);
798                animation.play(itemsAlpha);
799
800                // Invalidate the scrim throughout the animation to ensure the highlight
801                // cutout is correct throughout.
802                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
803                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
804                    @Override
805                    public void onAnimationUpdate(ValueAnimator animation) {
806                        mLauncher.getDragLayer().invalidateScrim();
807                    }
808                });
809                animation.play(invalidateScrim);
810
811                if (material) {
812                    // Animate the all apps button
813                    float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
814                    AnimatorListenerAdapter listener =
815                            pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
816                    Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
817                            revealRadius, finalRadius).createRevealAnimator(revealView);
818                    reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
819                    reveal.setDuration(revealDuration);
820                    reveal.setStartDelay(itemsAlphaStagger);
821                    if (listener != null) {
822                        reveal.addListener(listener);
823                    }
824                    animation.play(reveal);
825                }
826            }
827
828            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
829            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
830
831            animation.addListener(new AnimatorListenerAdapter() {
832                @Override
833                public void onAnimationEnd(Animator animation) {
834                    fromView.setVisibility(View.GONE);
835                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
836                    dispatchOnLauncherTransitionEnd(toView, animated, true);
837
838                    // Run any queued runnables
839                    if (onCompleteRunnable != null) {
840                        onCompleteRunnable.run();
841                    }
842
843                    // Disable all necessary layers
844                    for (View v : layerViews.keySet()) {
845                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
846                            v.setLayerType(View.LAYER_TYPE_NONE, null);
847                        }
848                    }
849
850                    // Reset page transforms
851                    if (contentView != null) {
852                        contentView.setTranslationX(0);
853                        contentView.setTranslationY(0);
854                        contentView.setAlpha(1);
855                    }
856
857                    // This can hold unnecessary references to views.
858                    cleanupAnimation();
859                    pCb.onTransitionComplete();
860                }
861            });
862
863            final AnimatorSet stateAnimation = animation;
864            final Runnable startAnimRunnable = new Runnable() {
865                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
866                public void run() {
867                    // Check that mCurrentAnimation hasn't changed while
868                    // we waited for a layout/draw pass
869                    if (mCurrentAnimation != stateAnimation)
870                        return;
871
872                    dispatchOnLauncherTransitionStart(fromView, animated, false);
873                    dispatchOnLauncherTransitionStart(toView, animated, false);
874
875                    // Enable all necessary layers
876                    for (View v : layerViews.keySet()) {
877                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
878                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
879                        }
880                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
881                            v.buildLayer();
882                        }
883                    }
884                    stateAnimation.start();
885                }
886            };
887            fromView.post(startAnimRunnable);
888
889            return animation;
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            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            fromView.post(startAnimRunnable);
956            return animation;
957        }
958        return null;
959    }
960
961    /**
962     * Dispatches the prepare-transition event to suitable views.
963     */
964    void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
965            boolean multiplePagesVisible) {
966        if (v instanceof LauncherTransitionable) {
967            ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
968                    multiplePagesVisible);
969        }
970    }
971
972    /**
973     * Dispatches the start-transition event to suitable views.
974     */
975    void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
976        if (v instanceof LauncherTransitionable) {
977            ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
978                    toWorkspace);
979        }
980
981        // Update the workspace transition step as well
982        dispatchOnLauncherTransitionStep(v, 0f);
983    }
984
985    /**
986     * Dispatches the step-transition event to suitable views.
987     */
988    void dispatchOnLauncherTransitionStep(View v, float t) {
989        if (v instanceof LauncherTransitionable) {
990            ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
991        }
992    }
993
994    /**
995     * Dispatches the end-transition event to suitable views.
996     */
997    void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
998        if (v instanceof LauncherTransitionable) {
999            ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
1000                    toWorkspace);
1001        }
1002
1003        // Update the workspace transition step as well
1004        dispatchOnLauncherTransitionStep(v, 1f);
1005    }
1006
1007    /**
1008     * Cancels the current animation.
1009     */
1010    private void cancelAnimation() {
1011        if (mCurrentAnimation != null) {
1012            mCurrentAnimation.setDuration(0);
1013            mCurrentAnimation.cancel();
1014            mCurrentAnimation = null;
1015        }
1016    }
1017
1018    @Thunk void cleanupAnimation() {
1019        mCurrentAnimation = null;
1020    }
1021}
1022