Home | History | Annotate | Download | only in launcher3
      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 
    101         PrivateTransitionCallbacks(float revealAlpha) {
    102             materialRevealViewFinalAlpha = revealAlpha;
    103         }
    104 
    105         float getMaterialRevealViewStartFinalRadius() {
    106             return 0;
    107         }
    108         AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
    109                 View buttonView) {
    110             return null;
    111         }
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    705     private void cancelAnimation() {
    706         if (mCurrentAnimation != null) {
    707             mCurrentAnimation.setDuration(0);
    708             mCurrentAnimation.cancel();
    709             mCurrentAnimation = null;
    710         }
    711     }
    712 
    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 
    722         public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
    723             mAnim = anim;
    724             mViewToFocus = viewToFocus;
    725         }
    726 
    727         @Override
    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