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