LauncherStateTransitionAnimation.java revision e3876e8dd0a6b6948c5365af9f88709301bbb536
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.animation.PropertyValuesHolder;
24import android.animation.TimeInterpolator;
25import android.animation.ValueAnimator;
26import android.annotation.SuppressLint;
27import android.annotation.TargetApi;
28import android.content.res.Resources;
29import android.os.Build;
30import android.util.Log;
31import android.view.View;
32import android.view.animation.AccelerateInterpolator;
33import android.view.animation.DecelerateInterpolator;
34
35import com.android.launcher3.allapps.AllAppsContainerView;
36import com.android.launcher3.allapps.AllAppsTransitionController;
37import com.android.launcher3.config.FeatureFlags;
38import com.android.launcher3.util.CircleRevealOutlineProvider;
39import com.android.launcher3.util.Thunk;
40import com.android.launcher3.widget.WidgetsContainerView;
41
42import java.util.HashMap;
43
44/**
45 * TODO: figure out what kind of tests we can write for this
46 *
47 * Things to test when changing the following class.
48 *   - Home from workspace
49 *          - from center screen
50 *          - from other screens
51 *   - Home from all apps
52 *          - from center screen
53 *          - from other screens
54 *   - Back from all apps
55 *          - from center screen
56 *          - from other screens
57 *   - Launch app from workspace and quit
58 *          - with back
59 *          - with home
60 *   - Launch app from all apps and quit
61 *          - with back
62 *          - with home
63 *   - Go to a screen that's not the default, then all
64 *     apps, and launch and app, and go back
65 *          - with back
66 *          -with home
67 *   - On workspace, long press power and go back
68 *          - with back
69 *          - with home
70 *   - On all apps, long press power and go back
71 *          - with back
72 *          - with home
73 *   - On workspace, power off
74 *   - On all apps, power off
75 *   - Launch an app and turn off the screen while in that app
76 *          - Go back with home key
77 *          - Go back with back key  TODO: make this not go to workspace
78 *          - From all apps
79 *          - From workspace
80 *   - Enter and exit car mode (becuase it causes an extra configuration changed)
81 *          - From all apps
82 *          - From the center workspace
83 *          - From another workspace
84 */
85public class LauncherStateTransitionAnimation {
86
87    /**
88     * animation used for all apps and widget tray when
89     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
90     */
91    public static final int CIRCULAR_REVEAL = 0;
92    /**
93     * animation used for all apps and not widget tray when
94     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
95     */
96    public static final int PULLUP = 1;
97
98    private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
99
100    /**
101     * Private callbacks made during transition setup.
102     */
103    private static class PrivateTransitionCallbacks {
104        private final float materialRevealViewFinalAlpha;
105
106        PrivateTransitionCallbacks(float revealAlpha) {
107            materialRevealViewFinalAlpha = revealAlpha;
108        }
109
110        float getMaterialRevealViewStartFinalRadius() {
111            return 0;
112        }
113        AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
114                View buttonView) {
115            return null;
116        }
117        void onTransitionComplete() {}
118    }
119
120    public static final String TAG = "LSTAnimation";
121
122    // Flags to determine how to set the layers on views before the transition animation
123    public static final int BUILD_LAYER = 0;
124    public static final int BUILD_AND_SET_LAYER = 1;
125    public static final int SINGLE_FRAME_DELAY = 16;
126
127    @Thunk Launcher mLauncher;
128    @Thunk AnimatorSet mCurrentAnimation;
129    AllAppsTransitionController mAllAppsController;
130
131    public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
132        mLauncher = l;
133        mAllAppsController = allAppsController;
134    }
135
136    /**
137     * Starts an animation to the apps view.
138     *
139     * @param startSearchAfterTransition Immediately starts app search after the transition to
140     *                                   All Apps is completed.
141     */
142    public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
143            final boolean animated, final boolean startSearchAfterTransition) {
144        final AllAppsContainerView toView = mLauncher.getAppsView();
145        final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
146        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
147            @Override
148            public float getMaterialRevealViewStartFinalRadius() {
149                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
150                return allAppsButtonSize / 2;
151            }
152            @Override
153            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
154                    final View revealView, final View allAppsButtonView) {
155                return new AnimatorListenerAdapter() {
156                    public void onAnimationStart(Animator animation) {
157                        allAppsButtonView.setVisibility(View.INVISIBLE);
158                    }
159                    public void onAnimationEnd(Animator animation) {
160                        allAppsButtonView.setVisibility(View.VISIBLE);
161                    }
162                };
163            }
164            @Override
165            void onTransitionComplete() {
166                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
167                if (startSearchAfterTransition) {
168                    toView.startAppsSearch();
169                }
170            }
171        };
172        int animType = CIRCULAR_REVEAL;
173        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
174            animType = PULLUP;
175        }
176        // Only animate the search bar if animating from spring loaded mode back to all apps
177        mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
178                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
179    }
180
181    /**
182     * Starts an animation to the widgets view.
183     */
184    public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
185            final boolean animated) {
186        final WidgetsContainerView toView = mLauncher.getWidgetsView();
187        final View buttonView = mLauncher.getWidgetsButton();
188        mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
189                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
190                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
191                    @Override
192                    void onTransitionComplete() {
193                        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
194                    }
195                });
196    }
197
198    /**
199     * Starts an animation to the workspace from the current overlay view.
200     */
201    public void startAnimationToWorkspace(final Launcher.State fromState,
202            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
203            final boolean animated, final Runnable onCompleteRunnable) {
204        if (toWorkspaceState != Workspace.State.NORMAL &&
205                toWorkspaceState != Workspace.State.SPRING_LOADED &&
206                toWorkspaceState != Workspace.State.OVERVIEW) {
207            Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
208        }
209
210        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
211                || mAllAppsController.isTransitioning()) {
212            int animType = CIRCULAR_REVEAL;
213            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
214                animType = PULLUP;
215            }
216            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
217                    animated, animType, onCompleteRunnable);
218        } else if (fromState == Launcher.State.WIDGETS ||
219                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
220            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
221                    animated, onCompleteRunnable);
222        } else {
223            startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
224                    animated, onCompleteRunnable);
225        }
226    }
227
228    /**
229     * Creates and starts a new animation to a particular overlay view.
230     */
231    @SuppressLint("NewApi")
232    private AnimatorSet startAnimationToOverlay(
233            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
234            final View buttonView, final BaseContainerView toView,
235            final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
236        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
237        final Resources res = mLauncher.getResources();
238        final boolean material = Utilities.ATLEAST_LOLLIPOP;
239        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
240        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
241
242        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
243
244        final View fromView = mLauncher.getWorkspace();
245
246        final HashMap<View, Integer> layerViews = new HashMap<>();
247
248        // If for some reason our views aren't initialized, don't animate
249        boolean initialized = buttonView != null;
250
251        // Cancel the current animation
252        cancelAnimation();
253
254        final View contentView = toView.getContentView();
255        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
256                animated, initialized, animation, layerViews);
257        if (!animated || !initialized) {
258            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
259                    toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
260                mAllAppsController.finishPullUp();
261            }
262            toView.setTranslationX(0.0f);
263            toView.setTranslationY(0.0f);
264            toView.setScaleX(1.0f);
265            toView.setScaleY(1.0f);
266            toView.setAlpha(1.0f);
267            toView.setVisibility(View.VISIBLE);
268            toView.bringToFront();
269
270            // Show the content view
271            contentView.setVisibility(View.VISIBLE);
272
273            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
274            dispatchOnLauncherTransitionStart(fromView, animated, false);
275            dispatchOnLauncherTransitionEnd(fromView, animated, false);
276            dispatchOnLauncherTransitionPrepare(toView, animated, false);
277            dispatchOnLauncherTransitionStart(toView, animated, false);
278            dispatchOnLauncherTransitionEnd(toView, animated, false);
279            pCb.onTransitionComplete();
280
281            return null;
282        }
283        if (animType == CIRCULAR_REVEAL) {
284            // Setup the reveal view animation
285            final View revealView = toView.getRevealView();
286
287            int width = revealView.getMeasuredWidth();
288            int height = revealView.getMeasuredHeight();
289            float revealRadius = (float) Math.hypot(width / 2, height / 2);
290            revealView.setVisibility(View.VISIBLE);
291            revealView.setAlpha(0f);
292            revealView.setTranslationY(0f);
293            revealView.setTranslationX(0f);
294
295            // Calculate the final animation values
296            final float revealViewToAlpha;
297            final float revealViewToXDrift;
298            final float revealViewToYDrift;
299            if (material) {
300                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
301                        revealView, buttonView, null);
302                revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
303                revealViewToYDrift = buttonViewToPanelDelta[1];
304                revealViewToXDrift = buttonViewToPanelDelta[0];
305            } else {
306                revealViewToAlpha = 0f;
307                revealViewToYDrift = 2 * height / 3;
308                revealViewToXDrift = 0;
309            }
310
311            // Create the animators
312            PropertyValuesHolder panelAlpha =
313                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
314            PropertyValuesHolder panelDriftY =
315                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
316            PropertyValuesHolder panelDriftX =
317                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
318            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
319                    panelAlpha, panelDriftY, panelDriftX);
320            panelAlphaAndDrift.setDuration(revealDuration);
321            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
322
323            // Play the animation
324            layerViews.put(revealView, BUILD_AND_SET_LAYER);
325            animation.play(panelAlphaAndDrift);
326
327            // Setup the animation for the content view
328            contentView.setVisibility(View.VISIBLE);
329            contentView.setAlpha(0f);
330            contentView.setTranslationY(revealViewToYDrift);
331            layerViews.put(contentView, BUILD_AND_SET_LAYER);
332
333            // Create the individual animators
334            ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
335                    revealViewToYDrift, 0);
336            pageDrift.setDuration(revealDuration);
337            pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
338            pageDrift.setStartDelay(itemsAlphaStagger);
339            animation.play(pageDrift);
340
341            ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
342            itemsAlpha.setDuration(revealDuration);
343            itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
344            itemsAlpha.setStartDelay(itemsAlphaStagger);
345            animation.play(itemsAlpha);
346
347            if (material) {
348                float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
349                AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
350                        revealView, buttonView);
351                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
352                        startRadius, revealRadius).createRevealAnimator(revealView);
353                reveal.setDuration(revealDuration);
354                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
355                if (listener != null) {
356                    reveal.addListener(listener);
357                }
358                animation.play(reveal);
359            }
360
361            animation.addListener(new AnimatorListenerAdapter() {
362                @Override
363                public void onAnimationEnd(Animator animation) {
364                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
365                    dispatchOnLauncherTransitionEnd(toView, animated, false);
366
367                    // Hide the reveal view
368                    revealView.setVisibility(View.INVISIBLE);
369
370                    // Disable all necessary layers
371                    for (View v : layerViews.keySet()) {
372                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
373                            v.setLayerType(View.LAYER_TYPE_NONE, null);
374                        }
375                    }
376
377                    // This can hold unnecessary references to views.
378                    cleanupAnimation();
379                    pCb.onTransitionComplete();
380                }
381
382            });
383
384            // Dispatch the prepare transition signal
385            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
386            dispatchOnLauncherTransitionPrepare(toView, animated, false);
387
388            final AnimatorSet stateAnimation = animation;
389            final Runnable startAnimRunnable = new Runnable() {
390                public void run() {
391                    // Check that mCurrentAnimation hasn't changed while
392                    // we waited for a layout/draw pass
393                    if (mCurrentAnimation != stateAnimation)
394                        return;
395                    dispatchOnLauncherTransitionStart(fromView, animated, false);
396                    dispatchOnLauncherTransitionStart(toView, animated, false);
397
398                    // Enable all necessary layers
399                    for (View v : layerViews.keySet()) {
400                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
401                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
402                        }
403                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
404                            v.buildLayer();
405                        }
406                    }
407
408                    // Focus the new view
409                    toView.requestFocus();
410
411                    stateAnimation.start();
412                }
413            };
414            toView.bringToFront();
415            toView.setVisibility(View.VISIBLE);
416            toView.post(startAnimRunnable);
417
418            return animation;
419        } else if (animType == PULLUP) {
420            // We are animating the content view alpha, so ensure we have a layer for it
421            layerViews.put(contentView, BUILD_AND_SET_LAYER);
422
423            animation.addListener(new AnimatorListenerAdapter() {
424                  @Override
425                  public void onAnimationEnd(Animator animation) {
426                      dispatchOnLauncherTransitionEnd(fromView, animated, false);
427                      dispatchOnLauncherTransitionEnd(toView, animated, false);
428
429                      // Disable all necessary layers
430                      for (View v : layerViews.keySet()) {
431                          if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
432                              v.setLayerType(View.LAYER_TYPE_NONE, null);
433                          }
434                      }
435
436                      cleanupAnimation();
437                      pCb.onTransitionComplete();
438                  }
439            });
440            mAllAppsController.animateToAllApps(animation, revealDurationSlide);
441
442            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
443            dispatchOnLauncherTransitionPrepare(toView, animated, false);
444
445            final AnimatorSet stateAnimation = animation;
446            final Runnable startAnimRunnable = new Runnable() {
447                public void run() {
448                    // Check that mCurrentAnimation hasn't changed while
449                    // we waited for a layout/draw pass
450                    if (mCurrentAnimation != stateAnimation)
451                        return;
452
453                    dispatchOnLauncherTransitionStart(fromView, animated, false);
454                    dispatchOnLauncherTransitionStart(toView, animated, false);
455
456                    // Enable all necessary layers
457                    for (View v : layerViews.keySet()) {
458                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
459                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
460                        }
461                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
462                            v.buildLayer();
463                        }
464                    }
465
466                    toView.requestFocus();
467                    stateAnimation.start();
468                }
469            };
470            toView.post(startAnimRunnable);
471            return animation;
472        }
473        return null;
474    }
475
476    /**
477     * Plays animations used by various transitions.
478     */
479    private void playCommonTransitionAnimations(
480            Workspace.State toWorkspaceState, View fromView, View toView,
481            boolean animated, boolean initialized, AnimatorSet animation,
482            HashMap<View, Integer> layerViews) {
483        // Create the workspace animation.
484        // NOTE: this call apparently also sets the state for the workspace if !animated
485        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
486                animated, layerViews);
487
488        if (animated && initialized) {
489            // Play the workspace animation
490            if (workspaceAnim != null) {
491                animation.play(workspaceAnim);
492            }
493            // Dispatch onLauncherTransitionStep() as the animation interpolates.
494            animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
495        }
496    }
497
498    /**
499     * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
500     * {@param fromView} and {@param toView} as the animation interpolates.
501     *
502     * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
503     */
504    private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
505        ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
506        updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
507            @Override
508            public void onAnimationUpdate(ValueAnimator animation) {
509                dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
510                dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
511            }
512        });
513        return updateAnimator;
514    }
515
516    /**
517     * Starts an animation to the workspace from the apps view.
518     */
519    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
520            final Workspace.State toWorkspaceState, final boolean animated, int type,
521            final Runnable onCompleteRunnable) {
522        AllAppsContainerView appsView = mLauncher.getAppsView();
523        // No alpha anim from all apps
524        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
525            @Override
526            float getMaterialRevealViewStartFinalRadius() {
527                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
528                return allAppsButtonSize / 2;
529            }
530            @Override
531            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
532                    final View revealView, final View allAppsButtonView) {
533                return new AnimatorListenerAdapter() {
534                    public void onAnimationStart(Animator animation) {
535                        // We set the alpha instead of visibility to ensure that the focus does not
536                        // get taken from the all apps view
537                        allAppsButtonView.setVisibility(View.VISIBLE);
538                        allAppsButtonView.setAlpha(0f);
539                    }
540                    public void onAnimationEnd(Animator animation) {
541                        // Hide the reveal view
542                        revealView.setVisibility(View.INVISIBLE);
543
544                        // Show the all apps button, and focus it
545                        allAppsButtonView.setAlpha(1f);
546                    }
547                };
548            }
549            @Override
550            void onTransitionComplete() {
551                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
552            }
553        };
554        // Only animate the search bar if animating to spring loaded mode from all apps
555        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
556                mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
557                animated, type, onCompleteRunnable, cb);
558    }
559
560    /**
561     * Starts an animation to the workspace from the widgets view.
562     */
563    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
564            final Workspace.State toWorkspaceState, final boolean animated,
565            final Runnable onCompleteRunnable) {
566        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
567        PrivateTransitionCallbacks cb =
568                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
569            @Override
570            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
571                    final View revealView, final View widgetsButtonView) {
572                return new AnimatorListenerAdapter() {
573                    public void onAnimationEnd(Animator animation) {
574                        // Hide the reveal view
575                        revealView.setVisibility(View.INVISIBLE);
576                    }
577                };
578            }
579            @Override
580            void onTransitionComplete() {
581                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
582            }
583        };
584        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(
585                fromWorkspaceState, toWorkspaceState,
586                mLauncher.getWidgetsButton(), widgetsView,
587                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
588    }
589
590    /**
591     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
592     */
593    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
594            final Workspace.State toWorkspaceState, final boolean animated,
595            final Runnable onCompleteRunnable) {
596        final View fromWorkspace = mLauncher.getWorkspace();
597        final HashMap<View, Integer> layerViews = new HashMap<>();
598        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
599        final int revealDuration = mLauncher.getResources()
600                .getInteger(R.integer.config_overlayRevealTime);
601
602        // Cancel the current animation
603        cancelAnimation();
604
605        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
606
607        playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
608                animated, animated, animation, layerViews);
609
610        if (animated) {
611            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
612
613            final AnimatorSet stateAnimation = animation;
614            final Runnable startAnimRunnable = new Runnable() {
615                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
616                public void run() {
617                    // Check that mCurrentAnimation hasn't changed while
618                    // we waited for a layout/draw pass
619                    if (mCurrentAnimation != stateAnimation)
620                        return;
621
622                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
623
624                    // Enable all necessary layers
625                    for (View v : layerViews.keySet()) {
626                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
627                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
628                        }
629                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
630                            v.buildLayer();
631                        }
632                    }
633                    stateAnimation.start();
634                }
635            };
636            animation.addListener(new AnimatorListenerAdapter() {
637                @Override
638                public void onAnimationEnd(Animator animation) {
639                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
640
641                    // Run any queued runnables
642                    if (onCompleteRunnable != null) {
643                        onCompleteRunnable.run();
644                    }
645
646                    // Disable all necessary layers
647                    for (View v : layerViews.keySet()) {
648                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
649                            v.setLayerType(View.LAYER_TYPE_NONE, null);
650                        }
651                    }
652
653                    // This can hold unnecessary references to views.
654                    cleanupAnimation();
655                }
656            });
657            fromWorkspace.post(startAnimRunnable);
658            mCurrentAnimation = animation;
659        } else /* if (!animated) */ {
660            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
661            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
662            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
663
664            // Run any queued runnables
665            if (onCompleteRunnable != null) {
666                onCompleteRunnable.run();
667            }
668
669            mCurrentAnimation = null;
670        }
671    }
672
673    /**
674     * Creates and starts a new animation to the workspace.
675     */
676    private AnimatorSet startAnimationToWorkspaceFromOverlay(
677            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
678            final View buttonView, final BaseContainerView fromView,
679            final boolean animated, int animType, final Runnable onCompleteRunnable,
680            final PrivateTransitionCallbacks pCb) {
681        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
682        final Resources res = mLauncher.getResources();
683        final boolean material = Utilities.ATLEAST_LOLLIPOP;
684        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
685        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
686        final int itemsAlphaStagger =
687                res.getInteger(R.integer.config_overlayItemsAlphaStagger);
688
689        final View toView = mLauncher.getWorkspace();
690        final View revealView = fromView.getRevealView();
691        final View contentView = fromView.getContentView();
692
693        final HashMap<View, Integer> layerViews = new HashMap<>();
694
695        // If for some reason our views aren't initialized, don't animate
696        boolean initialized = buttonView != null;
697
698        // Cancel the current animation
699        cancelAnimation();
700
701        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
702
703        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
704                animated, initialized, animation, layerViews);
705        if (!animated || !initialized) {
706            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
707                    fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
708                mAllAppsController.finishPullDown(false);
709            }
710            fromView.setVisibility(View.GONE);
711            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
712            dispatchOnLauncherTransitionStart(fromView, animated, true);
713            dispatchOnLauncherTransitionEnd(fromView, animated, true);
714            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
715            dispatchOnLauncherTransitionStart(toView, animated, true);
716            dispatchOnLauncherTransitionEnd(toView, animated, true);
717            pCb.onTransitionComplete();
718
719            // Run any queued runnables
720            if (onCompleteRunnable != null) {
721                onCompleteRunnable.run();
722            }
723            return null;
724        }
725        if (animType == CIRCULAR_REVEAL) {
726            // hideAppsCustomizeHelper is called in some cases when it is already hidden
727            // don't perform all these no-op animations. In particularly, this was causing
728            // the all-apps button to pop in and out.
729            if (fromView.getVisibility() == View.VISIBLE) {
730                int width = revealView.getMeasuredWidth();
731                int height = revealView.getMeasuredHeight();
732                float revealRadius = (float) Math.hypot(width / 2, height / 2);
733                revealView.setVisibility(View.VISIBLE);
734                revealView.setAlpha(1f);
735                revealView.setTranslationY(0);
736                layerViews.put(revealView, BUILD_AND_SET_LAYER);
737
738                // Calculate the final animation values
739                final float revealViewToXDrift;
740                final float revealViewToYDrift;
741                if (material) {
742                    int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
743                            buttonView, null);
744                    revealViewToYDrift = buttonViewToPanelDelta[1];
745                    revealViewToXDrift = buttonViewToPanelDelta[0];
746                } else {
747                    revealViewToYDrift = 2 * height / 3;
748                    revealViewToXDrift = 0;
749                }
750
751                // The vertical motion of the apps panel should be delayed by one frame
752                // from the conceal animation in order to give the right feel. We correspondingly
753                // shorten the duration so that the slide and conceal end at the same time.
754                TimeInterpolator decelerateInterpolator = material ?
755                        new LogDecelerateInterpolator(100, 0) :
756                        new DecelerateInterpolator(1f);
757                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
758                        0, revealViewToYDrift);
759                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
760                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
761                panelDriftY.setInterpolator(decelerateInterpolator);
762                animation.play(panelDriftY);
763
764                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
765                        0, revealViewToXDrift);
766                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
767                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
768                panelDriftX.setInterpolator(decelerateInterpolator);
769                animation.play(panelDriftX);
770
771                // Setup animation for the reveal panel alpha
772                final float revealViewToAlpha = !material ? 0f :
773                        pCb.materialRevealViewFinalAlpha;
774                if (revealViewToAlpha != 1f) {
775                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
776                            1f, revealViewToAlpha);
777                    panelAlpha.setDuration(material ? revealDuration : 150);
778                    panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
779                    panelAlpha.setInterpolator(decelerateInterpolator);
780                    animation.play(panelAlpha);
781                }
782
783                // Setup the animation for the content view
784                layerViews.put(contentView, BUILD_AND_SET_LAYER);
785
786                // Create the individual animators
787                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
788                        0, revealViewToYDrift);
789                contentView.setTranslationY(0);
790                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
791                pageDrift.setInterpolator(decelerateInterpolator);
792                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
793                animation.play(pageDrift);
794
795                contentView.setAlpha(1f);
796                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
797                itemsAlpha.setDuration(100);
798                itemsAlpha.setInterpolator(decelerateInterpolator);
799                animation.play(itemsAlpha);
800
801                // Invalidate the scrim throughout the animation to ensure the highlight
802                // cutout is correct throughout.
803                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
804                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
805                    @Override
806                    public void onAnimationUpdate(ValueAnimator animation) {
807                        mLauncher.getDragLayer().invalidateScrim();
808                    }
809                });
810                animation.play(invalidateScrim);
811
812                if (material) {
813                    // Animate the all apps button
814                    float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
815                    AnimatorListenerAdapter listener =
816                            pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
817                    Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
818                            revealRadius, finalRadius).createRevealAnimator(revealView);
819                    reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
820                    reveal.setDuration(revealDuration);
821                    reveal.setStartDelay(itemsAlphaStagger);
822                    if (listener != null) {
823                        reveal.addListener(listener);
824                    }
825                    animation.play(reveal);
826                }
827            }
828
829            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
830            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
831
832            animation.addListener(new AnimatorListenerAdapter() {
833                @Override
834                public void onAnimationEnd(Animator animation) {
835                    fromView.setVisibility(View.GONE);
836                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
837                    dispatchOnLauncherTransitionEnd(toView, animated, true);
838
839                    // Run any queued runnables
840                    if (onCompleteRunnable != null) {
841                        onCompleteRunnable.run();
842                    }
843
844                    // Disable all necessary layers
845                    for (View v : layerViews.keySet()) {
846                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
847                            v.setLayerType(View.LAYER_TYPE_NONE, null);
848                        }
849                    }
850
851                    // Reset page transforms
852                    if (contentView != null) {
853                        contentView.setTranslationX(0);
854                        contentView.setTranslationY(0);
855                        contentView.setAlpha(1);
856                    }
857
858                    // This can hold unnecessary references to views.
859                    cleanupAnimation();
860                    pCb.onTransitionComplete();
861                }
862            });
863
864            final AnimatorSet stateAnimation = animation;
865            final Runnable startAnimRunnable = new Runnable() {
866                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
867                public void run() {
868                    // Check that mCurrentAnimation hasn't changed while
869                    // we waited for a layout/draw pass
870                    if (mCurrentAnimation != stateAnimation)
871                        return;
872
873                    dispatchOnLauncherTransitionStart(fromView, animated, false);
874                    dispatchOnLauncherTransitionStart(toView, animated, false);
875
876                    // Enable all necessary layers
877                    for (View v : layerViews.keySet()) {
878                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
879                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
880                        }
881                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
882                            v.buildLayer();
883                        }
884                    }
885                    stateAnimation.start();
886                }
887            };
888            fromView.post(startAnimRunnable);
889
890            return animation;
891        } else if (animType == PULLUP) {
892            // We are animating the content view alpha, so ensure we have a layer for it
893            layerViews.put(contentView, BUILD_AND_SET_LAYER);
894
895            animation.addListener(new AnimatorListenerAdapter() {
896                boolean canceled = false;
897                @Override
898                public void onAnimationCancel(Animator animation) {
899                    canceled = true;
900                }
901
902                @Override
903                public void onAnimationEnd(Animator animation) {
904                    if (canceled) return;
905                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
906                    dispatchOnLauncherTransitionEnd(toView, animated, true);
907                    // Run any queued runnables
908                    if (onCompleteRunnable != null) {
909                        onCompleteRunnable.run();
910                    }
911
912                    // Disable all necessary layers
913                    for (View v : layerViews.keySet()) {
914                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
915                            v.setLayerType(View.LAYER_TYPE_NONE, null);
916                        }
917                    }
918
919                    cleanupAnimation();
920                    pCb.onTransitionComplete();
921                }
922
923            });
924            mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
925
926            // Dispatch the prepare transition signal
927            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
928            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
929
930            final AnimatorSet stateAnimation = animation;
931            final Runnable startAnimRunnable = new Runnable() {
932                public void run() {
933                    // Check that mCurrentAnimation hasn't changed while
934                    // we waited for a layout/draw pass
935                    if (mCurrentAnimation != stateAnimation)
936                        return;
937
938                    dispatchOnLauncherTransitionStart(fromView, animated, false);
939                    dispatchOnLauncherTransitionStart(toView, animated, false);
940
941                    // Enable all necessary layers
942                    for (View v : layerViews.keySet()) {
943                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
944                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
945                        }
946                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
947                            v.buildLayer();
948                        }
949                    }
950
951                    // Focus the new view
952                    toView.requestFocus();
953                    stateAnimation.start();
954                }
955            };
956            fromView.post(startAnimRunnable);
957            return animation;
958        }
959        return null;
960    }
961
962    /**
963     * Dispatches the prepare-transition event to suitable views.
964     */
965    void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
966            boolean multiplePagesVisible) {
967        if (v instanceof LauncherTransitionable) {
968            ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
969                    multiplePagesVisible);
970        }
971    }
972
973    /**
974     * Dispatches the start-transition event to suitable views.
975     */
976    void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
977        if (v instanceof LauncherTransitionable) {
978            ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
979                    toWorkspace);
980        }
981
982        // Update the workspace transition step as well
983        dispatchOnLauncherTransitionStep(v, 0f);
984    }
985
986    /**
987     * Dispatches the step-transition event to suitable views.
988     */
989    void dispatchOnLauncherTransitionStep(View v, float t) {
990        if (v instanceof LauncherTransitionable) {
991            ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
992        }
993    }
994
995    /**
996     * Dispatches the end-transition event to suitable views.
997     */
998    void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
999        if (v instanceof LauncherTransitionable) {
1000            ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
1001                    toWorkspace);
1002        }
1003
1004        // Update the workspace transition step as well
1005        dispatchOnLauncherTransitionStep(v, 1f);
1006    }
1007
1008    /**
1009     * Cancels the current animation.
1010     */
1011    private void cancelAnimation() {
1012        if (mCurrentAnimation != null) {
1013            mCurrentAnimation.setDuration(0);
1014            mCurrentAnimation.cancel();
1015            mCurrentAnimation = null;
1016        }
1017    }
1018
1019    @Thunk void cleanupAnimation() {
1020        mCurrentAnimation = null;
1021    }
1022}
1023