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