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