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