LauncherStateTransitionAnimation.java revision f3e35d93318190f995e6a0fc9d0441ac844b67e4
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        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
611
612        if (animated) {
613            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
614
615            final AnimatorSet stateAnimation = animation;
616            final Runnable startAnimRunnable = new Runnable() {
617                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
618                public void run() {
619                    // Check that mCurrentAnimation hasn't changed while
620                    // we waited for a layout/draw pass
621                    if (mCurrentAnimation != stateAnimation)
622                        return;
623
624                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
625
626                    // Enable all necessary layers
627                    for (View v : layerViews.keySet()) {
628                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
629                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
630                        }
631                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
632                            v.buildLayer();
633                        }
634                    }
635                    stateAnimation.start();
636                }
637            };
638            animation.addListener(new AnimatorListenerAdapter() {
639                @Override
640                public void onAnimationEnd(Animator animation) {
641                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
642
643                    // Run any queued runnables
644                    if (onCompleteRunnable != null) {
645                        onCompleteRunnable.run();
646                    }
647
648                    // Disable all necessary layers
649                    for (View v : layerViews.keySet()) {
650                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
651                            v.setLayerType(View.LAYER_TYPE_NONE, null);
652                        }
653                    }
654
655                    // This can hold unnecessary references to views.
656                    cleanupAnimation();
657                }
658            });
659            fromWorkspace.post(startAnimRunnable);
660            mCurrentAnimation = animation;
661        } else /* if (!animated) */ {
662            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
663            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
664            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
665
666            // Run any queued runnables
667            if (onCompleteRunnable != null) {
668                onCompleteRunnable.run();
669            }
670
671            mCurrentAnimation = null;
672        }
673    }
674
675    /**
676     * Creates and starts a new animation to the workspace.
677     */
678    private void startAnimationToWorkspaceFromOverlay(
679            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
680            final View buttonView, final BaseContainerView fromView,
681            final boolean animated, int animType, final Runnable onCompleteRunnable,
682            final PrivateTransitionCallbacks pCb) {
683        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
684        final Resources res = mLauncher.getResources();
685        final boolean material = Utilities.ATLEAST_LOLLIPOP;
686        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
687        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
688        final int itemsAlphaStagger =
689                res.getInteger(R.integer.config_overlayItemsAlphaStagger);
690
691        final View toView = mLauncher.getWorkspace();
692        final View revealView = fromView.getRevealView();
693        final View contentView = fromView.getContentView();
694
695        final HashMap<View, Integer> layerViews = new HashMap<>();
696
697        // If for some reason our views aren't initialized, don't animate
698        boolean initialized = buttonView != null;
699
700        // Cancel the current animation
701        cancelAnimation();
702
703        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
704
705        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
706                animated, initialized, animation, layerViews);
707        if (!animated || !initialized) {
708            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
709                    fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
710                mAllAppsController.finishPullDown();
711            }
712            fromView.setVisibility(View.GONE);
713            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
714            dispatchOnLauncherTransitionStart(fromView, animated, true);
715            dispatchOnLauncherTransitionEnd(fromView, animated, true);
716            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
717            dispatchOnLauncherTransitionStart(toView, animated, true);
718            dispatchOnLauncherTransitionEnd(toView, animated, true);
719            pCb.onTransitionComplete();
720
721            // Run any queued runnables
722            if (onCompleteRunnable != null) {
723                onCompleteRunnable.run();
724            }
725            return;
726        }
727        if (animType == CIRCULAR_REVEAL) {
728            // hideAppsCustomizeHelper is called in some cases when it is already hidden
729            // don't perform all these no-op animations. In particularly, this was causing
730            // the all-apps button to pop in and out.
731            if (fromView.getVisibility() == View.VISIBLE) {
732                int width = revealView.getMeasuredWidth();
733                int height = revealView.getMeasuredHeight();
734                float revealRadius = (float) Math.hypot(width / 2, height / 2);
735                revealView.setVisibility(View.VISIBLE);
736                revealView.setAlpha(1f);
737                revealView.setTranslationY(0);
738                layerViews.put(revealView, BUILD_AND_SET_LAYER);
739
740                // Calculate the final animation values
741                final float revealViewToXDrift;
742                final float revealViewToYDrift;
743                if (material) {
744                    int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
745                            buttonView, null);
746                    revealViewToYDrift = buttonViewToPanelDelta[1];
747                    revealViewToXDrift = buttonViewToPanelDelta[0];
748                } else {
749                    revealViewToYDrift = 2 * height / 3;
750                    revealViewToXDrift = 0;
751                }
752
753                // The vertical motion of the apps panel should be delayed by one frame
754                // from the conceal animation in order to give the right feel. We correspondingly
755                // shorten the duration so that the slide and conceal end at the same time.
756                TimeInterpolator decelerateInterpolator = material ?
757                        new LogDecelerateInterpolator(100, 0) :
758                        new DecelerateInterpolator(1f);
759                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
760                        0, revealViewToYDrift);
761                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
762                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
763                panelDriftY.setInterpolator(decelerateInterpolator);
764                animation.play(panelDriftY);
765
766                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
767                        0, revealViewToXDrift);
768                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
769                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
770                panelDriftX.setInterpolator(decelerateInterpolator);
771                animation.play(panelDriftX);
772
773                // Setup animation for the reveal panel alpha
774                final float revealViewToAlpha = !material ? 0f :
775                        pCb.materialRevealViewFinalAlpha;
776                if (revealViewToAlpha != 1f) {
777                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
778                            1f, revealViewToAlpha);
779                    panelAlpha.setDuration(material ? revealDuration : 150);
780                    panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
781                    panelAlpha.setInterpolator(decelerateInterpolator);
782                    animation.play(panelAlpha);
783                }
784
785                // Setup the animation for the content view
786                layerViews.put(contentView, BUILD_AND_SET_LAYER);
787
788                // Create the individual animators
789                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
790                        0, revealViewToYDrift);
791                contentView.setTranslationY(0);
792                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
793                pageDrift.setInterpolator(decelerateInterpolator);
794                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
795                animation.play(pageDrift);
796
797                contentView.setAlpha(1f);
798                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
799                itemsAlpha.setDuration(100);
800                itemsAlpha.setInterpolator(decelerateInterpolator);
801                animation.play(itemsAlpha);
802
803                // Invalidate the scrim throughout the animation to ensure the highlight
804                // cutout is correct throughout.
805                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
806                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
807                    @Override
808                    public void onAnimationUpdate(ValueAnimator animation) {
809                        mLauncher.getDragLayer().invalidateScrim();
810                    }
811                });
812                animation.play(invalidateScrim);
813
814                if (material) {
815                    // Animate the all apps button
816                    float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
817                    AnimatorListenerAdapter listener =
818                            pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
819                    Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
820                            revealRadius, finalRadius).createRevealAnimator(revealView);
821                    reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
822                    reveal.setDuration(revealDuration);
823                    reveal.setStartDelay(itemsAlphaStagger);
824                    if (listener != null) {
825                        reveal.addListener(listener);
826                    }
827                    animation.play(reveal);
828                }
829            }
830
831            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
832            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
833
834            animation.addListener(new AnimatorListenerAdapter() {
835                @Override
836                public void onAnimationEnd(Animator animation) {
837                    fromView.setVisibility(View.GONE);
838                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
839                    dispatchOnLauncherTransitionEnd(toView, animated, true);
840
841                    // Run any queued runnables
842                    if (onCompleteRunnable != null) {
843                        onCompleteRunnable.run();
844                    }
845
846                    // Disable all necessary layers
847                    for (View v : layerViews.keySet()) {
848                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
849                            v.setLayerType(View.LAYER_TYPE_NONE, null);
850                        }
851                    }
852
853                    // Reset page transforms
854                    if (contentView != null) {
855                        contentView.setTranslationX(0);
856                        contentView.setTranslationY(0);
857                        contentView.setAlpha(1);
858                    }
859
860                    // This can hold unnecessary references to views.
861                    cleanupAnimation();
862                    pCb.onTransitionComplete();
863                }
864            });
865
866            final AnimatorSet stateAnimation = animation;
867            final Runnable startAnimRunnable = new Runnable() {
868                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
869                public void run() {
870                    // Check that mCurrentAnimation hasn't changed while
871                    // we waited for a layout/draw pass
872                    if (mCurrentAnimation != stateAnimation)
873                        return;
874
875                    dispatchOnLauncherTransitionStart(fromView, animated, false);
876                    dispatchOnLauncherTransitionStart(toView, animated, false);
877
878                    // Enable all necessary layers
879                    for (View v : layerViews.keySet()) {
880                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
881                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
882                        }
883                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
884                            v.buildLayer();
885                        }
886                    }
887                    stateAnimation.start();
888                }
889            };
890            mCurrentAnimation = animation;
891            fromView.post(startAnimRunnable);
892        } else if (animType == PULLUP) {
893            // We are animating the content view alpha, so ensure we have a layer for it
894            layerViews.put(contentView, BUILD_AND_SET_LAYER);
895
896            animation.addListener(new AnimatorListenerAdapter() {
897                boolean canceled = false;
898                @Override
899                public void onAnimationCancel(Animator animation) {
900                    canceled = true;
901                }
902
903                @Override
904                public void onAnimationEnd(Animator animation) {
905                    if (canceled) return;
906                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
907                    dispatchOnLauncherTransitionEnd(toView, animated, true);
908                    // Run any queued runnables
909                    if (onCompleteRunnable != null) {
910                        onCompleteRunnable.run();
911                    }
912
913                    // Disable all necessary layers
914                    for (View v : layerViews.keySet()) {
915                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
916                            v.setLayerType(View.LAYER_TYPE_NONE, null);
917                        }
918                    }
919
920                    cleanupAnimation();
921                    pCb.onTransitionComplete();
922                }
923
924            });
925            boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
926
927            // Dispatch the prepare transition signal
928            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
929            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
930
931            final AnimatorSet stateAnimation = animation;
932            final Runnable startAnimRunnable = new Runnable() {
933                public void run() {
934                    // Check that mCurrentAnimation hasn't changed while
935                    // we waited for a layout/draw pass
936                    if (mCurrentAnimation != stateAnimation)
937                        return;
938
939                    dispatchOnLauncherTransitionStart(fromView, animated, false);
940                    dispatchOnLauncherTransitionStart(toView, animated, false);
941
942                    // Enable all necessary layers
943                    for (View v : layerViews.keySet()) {
944                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
945                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
946                        }
947                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
948                            v.buildLayer();
949                        }
950                    }
951
952                    // Focus the new view
953                    toView.requestFocus();
954                    stateAnimation.start();
955                }
956            };
957            mCurrentAnimation = animation;
958            if (shouldPost) {
959                fromView.post(startAnimRunnable);
960            } else {
961                startAnimRunnable.run();
962            }
963        }
964        return;
965    }
966
967    /**
968     * Dispatches the prepare-transition event to suitable views.
969     */
970    void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
971            boolean multiplePagesVisible) {
972        if (v instanceof LauncherTransitionable) {
973            ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
974                    multiplePagesVisible);
975        }
976    }
977
978    /**
979     * Dispatches the start-transition event to suitable views.
980     */
981    void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
982        if (v instanceof LauncherTransitionable) {
983            ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
984                    toWorkspace);
985        }
986
987        // Update the workspace transition step as well
988        dispatchOnLauncherTransitionStep(v, 0f);
989    }
990
991    /**
992     * Dispatches the step-transition event to suitable views.
993     */
994    void dispatchOnLauncherTransitionStep(View v, float t) {
995        if (v instanceof LauncherTransitionable) {
996            ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
997        }
998    }
999
1000    /**
1001     * Dispatches the end-transition event to suitable views.
1002     */
1003    void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
1004        if (v instanceof LauncherTransitionable) {
1005            ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
1006                    toWorkspace);
1007        }
1008
1009        // Update the workspace transition step as well
1010        dispatchOnLauncherTransitionStep(v, 1f);
1011    }
1012
1013    /**
1014     * Cancels the current animation.
1015     */
1016    private void cancelAnimation() {
1017        if (mCurrentAnimation != null) {
1018            mCurrentAnimation.setDuration(0);
1019            mCurrentAnimation.cancel();
1020            mCurrentAnimation = null;
1021        }
1022    }
1023
1024    @Thunk void cleanupAnimation() {
1025        mCurrentAnimation = null;
1026    }
1027}
1028