• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.launcher3;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.TimeInterpolator;
25 import android.animation.ValueAnimator;
26 import android.content.res.Resources;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.animation.AccelerateInterpolator;
30 
31 import com.android.launcher3.allapps.AllAppsContainerView;
32 import com.android.launcher3.allapps.AllAppsTransitionController;
33 import com.android.launcher3.anim.AnimationLayerSet;
34 import com.android.launcher3.config.FeatureFlags;
35 import com.android.launcher3.util.CircleRevealOutlineProvider;
36 import com.android.launcher3.util.Thunk;
37 import com.android.launcher3.widget.WidgetsContainerView;
38 
39 /**
40  * TODO: figure out what kind of tests we can write for this
41  *
42  * Things to test when changing the following class.
43  *   - Home from workspace
44  *          - from center screen
45  *          - from other screens
46  *   - Home from all apps
47  *          - from center screen
48  *          - from other screens
49  *   - Back from all apps
50  *          - from center screen
51  *          - from other screens
52  *   - Launch app from workspace and quit
53  *          - with back
54  *          - with home
55  *   - Launch app from all apps and quit
56  *          - with back
57  *          - with home
58  *   - Go to a screen that's not the default, then all
59  *     apps, and launch and app, and go back
60  *          - with back
61  *          -with home
62  *   - On workspace, long press power and go back
63  *          - with back
64  *          - with home
65  *   - On all apps, long press power and go back
66  *          - with back
67  *          - with home
68  *   - On workspace, power off
69  *   - On all apps, power off
70  *   - Launch an app and turn off the screen while in that app
71  *          - Go back with home key
72  *          - Go back with back key  TODO: make this not go to workspace
73  *          - From all apps
74  *          - From workspace
75  *   - Enter and exit car mode (becuase it causes an extra configuration changed)
76  *          - From all apps
77  *          - From the center workspace
78  *          - From another workspace
79  */
80 public class LauncherStateTransitionAnimation {
81 
82     /**
83      * animation used for all apps and widget tray when
84      *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
85      */
86     public static final int CIRCULAR_REVEAL = 0;
87     /**
88      * animation used for all apps and not widget tray when
89      *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
90      */
91     public static final int PULLUP = 1;
92 
93     private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
94 
95     /**
96      * Private callbacks made during transition setup.
97      */
98     private static class PrivateTransitionCallbacks {
99         private final float materialRevealViewFinalAlpha;
100 
PrivateTransitionCallbacks(float revealAlpha)101         PrivateTransitionCallbacks(float revealAlpha) {
102             materialRevealViewFinalAlpha = revealAlpha;
103         }
104 
getMaterialRevealViewStartFinalRadius()105         float getMaterialRevealViewStartFinalRadius() {
106             return 0;
107         }
getMaterialRevealViewAnimatorListener(View revealView, View buttonView)108         AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
109                 View buttonView) {
110             return null;
111         }
onTransitionComplete()112         void onTransitionComplete() {}
113     }
114 
115     public static final String TAG = "LSTAnimation";
116 
117     public static final int SINGLE_FRAME_DELAY = 16;
118 
119     @Thunk Launcher mLauncher;
120     @Thunk AnimatorSet mCurrentAnimation;
121     AllAppsTransitionController mAllAppsController;
122 
LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController)123     public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
124         mLauncher = l;
125         mAllAppsController = allAppsController;
126     }
127 
128     /**
129      * Starts an animation to the apps view.
130      *
131      * @param startSearchAfterTransition Immediately starts app search after the transition to
132      *                                   All Apps is completed.
133      */
startAnimationToAllApps( final boolean animated, final boolean startSearchAfterTransition)134     public void startAnimationToAllApps(
135             final boolean animated, final boolean startSearchAfterTransition) {
136         final AllAppsContainerView toView = mLauncher.getAppsView();
137         final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
138         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
139             @Override
140             public float getMaterialRevealViewStartFinalRadius() {
141                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
142                 return allAppsButtonSize / 2;
143             }
144             @Override
145             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
146                     final View revealView, final View allAppsButtonView) {
147                 return new AnimatorListenerAdapter() {
148                     public void onAnimationStart(Animator animation) {
149                         allAppsButtonView.setVisibility(View.INVISIBLE);
150                     }
151                     public void onAnimationEnd(Animator animation) {
152                         allAppsButtonView.setVisibility(View.VISIBLE);
153                     }
154                 };
155             }
156             @Override
157             void onTransitionComplete() {
158                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
159                 if (startSearchAfterTransition) {
160                     toView.startAppsSearch();
161                 }
162             }
163         };
164         int animType = CIRCULAR_REVEAL;
165         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
166             animType = PULLUP;
167         }
168         // Only animate the search bar if animating from spring loaded mode back to all apps
169         startAnimationToOverlay(
170                 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
171     }
172 
173     /**
174      * Starts an animation to the widgets view.
175      */
startAnimationToWidgets(final boolean animated)176     public void startAnimationToWidgets(final boolean animated) {
177         final WidgetsContainerView toView = mLauncher.getWidgetsView();
178         final View buttonView = mLauncher.getWidgetsButton();
179         startAnimationToOverlay(
180                 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
181                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
182                     @Override
183                     void onTransitionComplete() {
184                         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
185                     }
186                 });
187     }
188 
189     /**
190      * Starts an animation to the workspace from the current overlay view.
191      */
startAnimationToWorkspace(final Launcher.State fromState, final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final boolean animated, final Runnable onCompleteRunnable)192     public void startAnimationToWorkspace(final Launcher.State fromState,
193             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
194             final boolean animated, final Runnable onCompleteRunnable) {
195         if (toWorkspaceState != Workspace.State.NORMAL &&
196                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
197                 toWorkspaceState != Workspace.State.OVERVIEW) {
198             Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
199         }
200 
201         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
202                 || mAllAppsController.isTransitioning()) {
203             int animType = CIRCULAR_REVEAL;
204             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
205                 animType = PULLUP;
206             }
207             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
208                     animated, animType, onCompleteRunnable);
209         } else if (fromState == Launcher.State.WIDGETS ||
210                 fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
211             startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
212                     animated, onCompleteRunnable);
213         } else {
214             startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
215                     animated, onCompleteRunnable);
216         }
217     }
218 
219     /**
220      * Creates and starts a new animation to a particular overlay view.
221      */
startAnimationToOverlay( final Workspace.State toWorkspaceState, final View buttonView, final BaseContainerView toView, final boolean animated, int animType, final PrivateTransitionCallbacks pCb)222     private void startAnimationToOverlay(
223             final Workspace.State toWorkspaceState,
224             final View buttonView, final BaseContainerView toView,
225             final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
226         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
227         final Resources res = mLauncher.getResources();
228         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
229         final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
230 
231         final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
232 
233         final AnimationLayerSet layerViews = new AnimationLayerSet();
234 
235         // If for some reason our views aren't initialized, don't animate
236         boolean initialized = buttonView != null;
237 
238         // Cancel the current animation
239         cancelAnimation();
240 
241         final View contentView = toView.getContentView();
242         playCommonTransitionAnimations(toWorkspaceState,
243                 animated, initialized, animation, layerViews);
244         if (!animated || !initialized) {
245             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
246                     toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
247                 mAllAppsController.finishPullUp();
248             }
249             toView.setTranslationX(0.0f);
250             toView.setTranslationY(0.0f);
251             toView.setScaleX(1.0f);
252             toView.setScaleY(1.0f);
253             toView.setAlpha(1.0f);
254             toView.setVisibility(View.VISIBLE);
255 
256             // Show the content view
257             contentView.setVisibility(View.VISIBLE);
258             pCb.onTransitionComplete();
259             return;
260         }
261         if (animType == CIRCULAR_REVEAL) {
262             // Setup the reveal view animation
263             final View revealView = toView.getRevealView();
264 
265             int width = revealView.getMeasuredWidth();
266             int height = revealView.getMeasuredHeight();
267             float revealRadius = (float) Math.hypot(width / 2, height / 2);
268             revealView.setVisibility(View.VISIBLE);
269             revealView.setAlpha(0f);
270             revealView.setTranslationY(0f);
271             revealView.setTranslationX(0f);
272 
273             // Calculate the final animation values
274             int[] buttonViewToPanelDelta =
275                     Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
276             final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
277             final float revealViewToXDrift = buttonViewToPanelDelta[0];
278             final float revealViewToYDrift = buttonViewToPanelDelta[1];
279 
280             // Create the animators
281             PropertyValuesHolder panelAlpha =
282                     PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
283             PropertyValuesHolder panelDriftY =
284                     PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
285             PropertyValuesHolder panelDriftX =
286                     PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
287             ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
288                     panelAlpha, panelDriftY, panelDriftX);
289             panelAlphaAndDrift.setDuration(revealDuration);
290             panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
291 
292             // Play the animation
293             layerViews.addView(revealView);
294             animation.play(panelAlphaAndDrift);
295 
296             // Setup the animation for the content view
297             contentView.setVisibility(View.VISIBLE);
298             contentView.setAlpha(0f);
299             contentView.setTranslationY(revealViewToYDrift);
300             layerViews.addView(contentView);
301 
302             // Create the individual animators
303             ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
304                     revealViewToYDrift, 0);
305             pageDrift.setDuration(revealDuration);
306             pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
307             pageDrift.setStartDelay(itemsAlphaStagger);
308             animation.play(pageDrift);
309 
310             ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
311             itemsAlpha.setDuration(revealDuration);
312             itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
313             itemsAlpha.setStartDelay(itemsAlphaStagger);
314             animation.play(itemsAlpha);
315 
316             float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
317             AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
318                     revealView, buttonView);
319             Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
320                     startRadius, revealRadius).createRevealAnimator(revealView);
321             reveal.setDuration(revealDuration);
322             reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
323             if (listener != null) {
324                 reveal.addListener(listener);
325             }
326             animation.play(reveal);
327 
328             animation.addListener(new AnimatorListenerAdapter() {
329                 @Override
330                 public void onAnimationEnd(Animator animation) {
331                     // Hide the reveal view
332                     revealView.setVisibility(View.INVISIBLE);
333 
334                     // This can hold unnecessary references to views.
335                     cleanupAnimation();
336                     pCb.onTransitionComplete();
337                 }
338 
339             });
340 
341             toView.bringToFront();
342             toView.setVisibility(View.VISIBLE);
343 
344             animation.addListener(layerViews);
345             toView.post(new StartAnimRunnable(animation, toView));
346             mCurrentAnimation = animation;
347         } else if (animType == PULLUP) {
348             // We are animating the content view alpha, so ensure we have a layer for it
349             layerViews.addView(contentView);
350 
351             animation.addListener(new AnimatorListenerAdapter() {
352                 @Override
353                 public void onAnimationEnd(Animator animation) {
354                     cleanupAnimation();
355                     pCb.onTransitionComplete();
356                 }
357             });
358             boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
359 
360             Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
361             mCurrentAnimation = animation;
362             mCurrentAnimation.addListener(layerViews);
363             if (shouldPost) {
364                 toView.post(startAnimRunnable);
365             } else {
366                 startAnimRunnable.run();
367             }
368         }
369     }
370 
371     /**
372      * Plays animations used by various transitions.
373      */
playCommonTransitionAnimations( Workspace.State toWorkspaceState, boolean animated, boolean initialized, AnimatorSet animation, AnimationLayerSet layerViews)374     private void playCommonTransitionAnimations(
375             Workspace.State toWorkspaceState,
376             boolean animated, boolean initialized, AnimatorSet animation,
377             AnimationLayerSet layerViews) {
378         // Create the workspace animation.
379         // NOTE: this call apparently also sets the state for the workspace if !animated
380         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
381                 animated, layerViews);
382 
383         if (animated && initialized) {
384             // Play the workspace animation
385             if (workspaceAnim != null) {
386                 animation.play(workspaceAnim);
387             }
388         }
389     }
390 
391     /**
392      * Starts an animation to the workspace from the apps view.
393      */
startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final boolean animated, int type, final Runnable onCompleteRunnable)394     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
395             final Workspace.State toWorkspaceState, final boolean animated, int type,
396             final Runnable onCompleteRunnable) {
397         final AllAppsContainerView appsView = mLauncher.getAppsView();
398         // No alpha anim from all apps
399         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
400             @Override
401             float getMaterialRevealViewStartFinalRadius() {
402                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
403                 return allAppsButtonSize / 2;
404             }
405             @Override
406             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
407                     final View revealView, final View allAppsButtonView) {
408                 return new AnimatorListenerAdapter() {
409                     public void onAnimationStart(Animator animation) {
410                         // We set the alpha instead of visibility to ensure that the focus does not
411                         // get taken from the all apps view
412                         allAppsButtonView.setVisibility(View.VISIBLE);
413                         allAppsButtonView.setAlpha(0f);
414                     }
415                     public void onAnimationEnd(Animator animation) {
416                         // Hide the reveal view
417                         revealView.setVisibility(View.INVISIBLE);
418 
419                         // Show the all apps button, and focus it
420                         allAppsButtonView.setAlpha(1f);
421                     }
422                 };
423             }
424             @Override
425             void onTransitionComplete() {
426                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
427                 appsView.reset();
428             }
429         };
430         // Only animate the search bar if animating to spring loaded mode from all apps
431         startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
432                 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
433                 animated, type, onCompleteRunnable, cb);
434     }
435 
436     /**
437      * Starts an animation to the workspace from the widgets view.
438      */
startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final boolean animated, final Runnable onCompleteRunnable)439     private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
440             final Workspace.State toWorkspaceState, final boolean animated,
441             final Runnable onCompleteRunnable) {
442         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
443         PrivateTransitionCallbacks cb =
444                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
445             @Override
446             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
447                     final View revealView, final View widgetsButtonView) {
448                 return new AnimatorListenerAdapter() {
449                     public void onAnimationEnd(Animator animation) {
450                         // Hide the reveal view
451                         revealView.setVisibility(View.INVISIBLE);
452                     }
453                 };
454             }
455             @Override
456             void onTransitionComplete() {
457                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
458             }
459         };
460         startAnimationToWorkspaceFromOverlay(
461                 fromWorkspaceState, toWorkspaceState,
462                 mLauncher.getWidgetsButton(), widgetsView,
463                 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
464     }
465 
466     /**
467      * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
468      */
startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final boolean animated, final Runnable onCompleteRunnable)469     private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
470             final Workspace.State toWorkspaceState, final boolean animated,
471             final Runnable onCompleteRunnable) {
472         final View fromWorkspace = mLauncher.getWorkspace();
473         final AnimationLayerSet layerViews = new AnimationLayerSet();
474         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
475 
476         // Cancel the current animation
477         cancelAnimation();
478 
479         playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
480         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
481 
482         if (animated) {
483             animation.addListener(new AnimatorListenerAdapter() {
484                 @Override
485                 public void onAnimationEnd(Animator animation) {
486                     // Run any queued runnables
487                     if (onCompleteRunnable != null) {
488                         onCompleteRunnable.run();
489                     }
490 
491                     // This can hold unnecessary references to views.
492                     cleanupAnimation();
493                 }
494             });
495             animation.addListener(layerViews);
496             fromWorkspace.post(new StartAnimRunnable(animation, null));
497             mCurrentAnimation = animation;
498         } else /* if (!animated) */ {
499             // Run any queued runnables
500             if (onCompleteRunnable != null) {
501                 onCompleteRunnable.run();
502             }
503 
504             mCurrentAnimation = null;
505         }
506     }
507 
508     /**
509      * Creates and starts a new animation to the workspace.
510      */
startAnimationToWorkspaceFromOverlay( final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final View buttonView, final BaseContainerView fromView, final boolean animated, int animType, final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb)511     private void startAnimationToWorkspaceFromOverlay(
512             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
513             final View buttonView, final BaseContainerView fromView,
514             final boolean animated, int animType, final Runnable onCompleteRunnable,
515             final PrivateTransitionCallbacks pCb) {
516         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
517         final Resources res = mLauncher.getResources();
518         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
519         final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
520         final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
521 
522         final View toView = mLauncher.getWorkspace();
523         final View revealView = fromView.getRevealView();
524         final View contentView = fromView.getContentView();
525 
526         final AnimationLayerSet layerViews = new AnimationLayerSet();
527 
528         // If for some reason our views aren't initialized, don't animate
529         boolean initialized = buttonView != null;
530 
531         // Cancel the current animation
532         cancelAnimation();
533 
534         playCommonTransitionAnimations(toWorkspaceState,
535                 animated, initialized, animation, layerViews);
536         if (!animated || !initialized) {
537             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
538                     fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
539                 mAllAppsController.finishPullDown();
540             }
541             fromView.setVisibility(View.GONE);
542             pCb.onTransitionComplete();
543 
544             // Run any queued runnables
545             if (onCompleteRunnable != null) {
546                 onCompleteRunnable.run();
547             }
548             return;
549         }
550         if (animType == CIRCULAR_REVEAL) {
551             // hideAppsCustomizeHelper is called in some cases when it is already hidden
552             // don't perform all these no-op animations. In particularly, this was causing
553             // the all-apps button to pop in and out.
554             if (fromView.getVisibility() == View.VISIBLE) {
555                 int width = revealView.getMeasuredWidth();
556                 int height = revealView.getMeasuredHeight();
557                 float revealRadius = (float) Math.hypot(width / 2, height / 2);
558                 revealView.setVisibility(View.VISIBLE);
559                 revealView.setAlpha(1f);
560                 revealView.setTranslationY(0);
561                 layerViews.addView(revealView);
562 
563                 // Calculate the final animation values
564                 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
565                 final float revealViewToXDrift = buttonViewToPanelDelta[0];
566                 final float revealViewToYDrift = buttonViewToPanelDelta[1];
567 
568                 // The vertical motion of the apps panel should be delayed by one frame
569                 // from the conceal animation in order to give the right feel. We correspondingly
570                 // shorten the duration so that the slide and conceal end at the same time.
571                 TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
572                 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
573                         0, revealViewToYDrift);
574                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
575                 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
576                 panelDriftY.setInterpolator(decelerateInterpolator);
577                 animation.play(panelDriftY);
578 
579                 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
580                         0, revealViewToXDrift);
581                 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
582                 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
583                 panelDriftX.setInterpolator(decelerateInterpolator);
584                 animation.play(panelDriftX);
585 
586                 // Setup animation for the reveal panel alpha
587                 if (pCb.materialRevealViewFinalAlpha != 1f) {
588                     ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
589                             1f, pCb.materialRevealViewFinalAlpha);
590                     panelAlpha.setDuration(revealDuration);
591                     panelAlpha.setInterpolator(decelerateInterpolator);
592                     animation.play(panelAlpha);
593                 }
594 
595                 // Setup the animation for the content view
596                 layerViews.addView(contentView);
597 
598                 // Create the individual animators
599                 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
600                         0, revealViewToYDrift);
601                 contentView.setTranslationY(0);
602                 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
603                 pageDrift.setInterpolator(decelerateInterpolator);
604                 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
605                 animation.play(pageDrift);
606 
607                 contentView.setAlpha(1f);
608                 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
609                 itemsAlpha.setDuration(100);
610                 itemsAlpha.setInterpolator(decelerateInterpolator);
611                 animation.play(itemsAlpha);
612 
613                 // Invalidate the scrim throughout the animation to ensure the highlight
614                 // cutout is correct throughout.
615                 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
616                 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
617                     @Override
618                     public void onAnimationUpdate(ValueAnimator animation) {
619                         mLauncher.getDragLayer().invalidateScrim();
620                     }
621                 });
622                 animation.play(invalidateScrim);
623 
624                 // Animate the all apps button
625                 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
626                 AnimatorListenerAdapter listener =
627                         pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
628                 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
629                         revealRadius, finalRadius).createRevealAnimator(revealView);
630                 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
631                 reveal.setDuration(revealDuration);
632                 reveal.setStartDelay(itemsAlphaStagger);
633                 if (listener != null) {
634                     reveal.addListener(listener);
635                 }
636                 animation.play(reveal);
637             }
638 
639             animation.addListener(new AnimatorListenerAdapter() {
640                 @Override
641                 public void onAnimationEnd(Animator animation) {
642                     fromView.setVisibility(View.GONE);
643                     // Run any queued runnables
644                     if (onCompleteRunnable != null) {
645                         onCompleteRunnable.run();
646                     }
647 
648                     // Reset page transforms
649                     if (contentView != null) {
650                         contentView.setTranslationX(0);
651                         contentView.setTranslationY(0);
652                         contentView.setAlpha(1);
653                     }
654 
655                     // This can hold unnecessary references to views.
656                     cleanupAnimation();
657                     pCb.onTransitionComplete();
658                 }
659             });
660 
661             mCurrentAnimation = animation;
662             mCurrentAnimation.addListener(layerViews);
663             fromView.post(new StartAnimRunnable(animation, null));
664         } else if (animType == PULLUP) {
665             // We are animating the content view alpha, so ensure we have a layer for it
666             layerViews.addView(contentView);
667 
668             animation.addListener(new AnimatorListenerAdapter() {
669                 boolean canceled = false;
670                 @Override
671                 public void onAnimationCancel(Animator animation) {
672                     canceled = true;
673                 }
674 
675                 @Override
676                 public void onAnimationEnd(Animator animation) {
677                     if (canceled) return;
678                     // Run any queued runnables
679                     if (onCompleteRunnable != null) {
680                         onCompleteRunnable.run();
681                     }
682 
683                     cleanupAnimation();
684                     pCb.onTransitionComplete();
685                 }
686 
687             });
688             boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
689 
690             Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
691             mCurrentAnimation = animation;
692             mCurrentAnimation.addListener(layerViews);
693             if (shouldPost) {
694                 fromView.post(startAnimRunnable);
695             } else {
696                 startAnimRunnable.run();
697             }
698         }
699         return;
700     }
701 
702     /**
703      * Cancels the current animation.
704      */
cancelAnimation()705     private void cancelAnimation() {
706         if (mCurrentAnimation != null) {
707             mCurrentAnimation.setDuration(0);
708             mCurrentAnimation.cancel();
709             mCurrentAnimation = null;
710         }
711     }
712 
cleanupAnimation()713     @Thunk void cleanupAnimation() {
714         mCurrentAnimation = null;
715     }
716 
717     private class StartAnimRunnable implements Runnable {
718 
719         private final AnimatorSet mAnim;
720         private final View mViewToFocus;
721 
StartAnimRunnable(AnimatorSet anim, View viewToFocus)722         public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
723             mAnim = anim;
724             mViewToFocus = viewToFocus;
725         }
726 
727         @Override
run()728         public void run() {
729             if (mCurrentAnimation != mAnim) {
730                 return;
731             }
732             if (mViewToFocus != null) {
733                 mViewToFocus.requestFocus();
734             }
735             mAnim.start();
736         }
737     }
738 }
739