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.annotation.SuppressLint;
     26 import android.content.res.Resources;
     27 import android.util.Log;
     28 import android.view.View;
     29 import android.view.animation.AccelerateInterpolator;
     30 import android.view.animation.DecelerateInterpolator;
     31 
     32 import com.android.launcher3.allapps.AllAppsContainerView;
     33 import com.android.launcher3.util.UiThreadCircularReveal;
     34 import com.android.launcher3.util.Thunk;
     35 import com.android.launcher3.widget.WidgetsContainerView;
     36 
     37 import java.util.HashMap;
     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      * Callbacks made during the state transition
     84      */
     85     interface Callbacks {
     86         public void onStateTransitionHideSearchBar();
     87     }
     88 
     89     /**
     90      * Private callbacks made during transition setup.
     91      */
     92     static abstract class PrivateTransitionCallbacks {
     93         float getMaterialRevealViewFinalAlpha(View revealView) {
     94             return 0;
     95         }
     96         float getMaterialRevealViewStartFinalRadius() {
     97             return 0;
     98         }
     99         AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
    100                 View buttonView) {
    101             return null;
    102         }
    103         void onTransitionComplete() {}
    104     }
    105 
    106     public static final String TAG = "LauncherStateTransitionAnimation";
    107 
    108     // Flags to determine how to set the layers on views before the transition animation
    109     public static final int BUILD_LAYER = 0;
    110     public static final int BUILD_AND_SET_LAYER = 1;
    111     public static final int SINGLE_FRAME_DELAY = 16;
    112 
    113     @Thunk Launcher mLauncher;
    114     @Thunk Callbacks mCb;
    115     @Thunk AnimatorSet mStateAnimation;
    116 
    117     public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
    118         mLauncher = l;
    119         mCb = cb;
    120     }
    121 
    122     /**
    123      * Starts an animation to the apps view.
    124      *
    125      * @param startSearchAfterTransition Immediately starts app search after the transition to
    126      *                                   All Apps is completed.
    127      */
    128     public void startAnimationToAllApps(final boolean animated,
    129             final boolean startSearchAfterTransition) {
    130         final AllAppsContainerView toView = mLauncher.getAppsView();
    131         final View buttonView = mLauncher.getAllAppsButton();
    132         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
    133             @Override
    134             public float getMaterialRevealViewFinalAlpha(View revealView) {
    135                 return 1f;
    136             }
    137             @Override
    138             public float getMaterialRevealViewStartFinalRadius() {
    139                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
    140                 return allAppsButtonSize / 2;
    141             }
    142             @Override
    143             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
    144                     final View revealView, final View allAppsButtonView) {
    145                 return new AnimatorListenerAdapter() {
    146                     public void onAnimationStart(Animator animation) {
    147                         allAppsButtonView.setVisibility(View.INVISIBLE);
    148                     }
    149                     public void onAnimationEnd(Animator animation) {
    150                         allAppsButtonView.setVisibility(View.VISIBLE);
    151                     }
    152                 };
    153             }
    154             @Override
    155             void onTransitionComplete() {
    156                 if (startSearchAfterTransition) {
    157                     toView.startAppsSearch();
    158                 }
    159             }
    160         };
    161         // Only animate the search bar if animating from spring loaded mode back to all apps
    162         startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, buttonView, toView,
    163                 toView.getContentView(), toView.getRevealView(), toView.getSearchBarView(),
    164                 animated, true /* hideSearchBar */, cb);
    165     }
    166 
    167     /**
    168      * Starts an animation to the widgets view.
    169      */
    170     public void startAnimationToWidgets(final boolean animated) {
    171         final WidgetsContainerView toView = mLauncher.getWidgetsView();
    172         final View buttonView = mLauncher.getWidgetsButton();
    173 
    174         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
    175             @Override
    176             public float getMaterialRevealViewFinalAlpha(View revealView) {
    177                 return 0.3f;
    178             }
    179         };
    180         startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, buttonView, toView,
    181                 toView.getContentView(), toView.getRevealView(), null, animated,
    182                 true /* hideSearchBar */, cb);
    183     }
    184 
    185     /**
    186      * Starts and animation to the workspace from the current overlay view.
    187      */
    188     public void startAnimationToWorkspace(final Launcher.State fromState,
    189               final Workspace.State toWorkspaceState, final int toWorkspacePage,
    190               final boolean animated, final Runnable onCompleteRunnable) {
    191         if (toWorkspaceState != Workspace.State.NORMAL &&
    192                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
    193                 toWorkspaceState != Workspace.State.OVERVIEW) {
    194             Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
    195         }
    196 
    197         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
    198             startAnimationToWorkspaceFromAllApps(toWorkspaceState, toWorkspacePage,
    199                     animated, onCompleteRunnable);
    200         } else {
    201             startAnimationToWorkspaceFromWidgets(toWorkspaceState, toWorkspacePage,
    202                     animated, onCompleteRunnable);
    203         }
    204     }
    205 
    206     /**
    207      * Creates and starts a new animation to a particular overlay view.
    208      */
    209     @SuppressLint("NewApi")
    210     private void startAnimationToOverlay(final Workspace.State toWorkspaceState,
    211             final View buttonView, final View toView, final View contentView, final View revealView,
    212             final View overlaySearchBarView, final boolean animated, final boolean hideSearchBar,
    213             final PrivateTransitionCallbacks pCb) {
    214         final Resources res = mLauncher.getResources();
    215         final boolean material = Utilities.isLmpOrAbove();
    216         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
    217         final int itemsAlphaStagger =
    218                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
    219 
    220         final View fromView = mLauncher.getWorkspace();
    221 
    222         final HashMap<View, Integer> layerViews = new HashMap<>();
    223 
    224         // If for some reason our views aren't initialized, don't animate
    225         boolean initialized = buttonView != null;
    226 
    227         // Cancel the current animation
    228         cancelAnimation();
    229 
    230         // Create the workspace animation.
    231         // NOTE: this call apparently also sets the state for the workspace if !animated
    232         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
    233                 animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
    234 
    235         if (animated && initialized) {
    236             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
    237 
    238             // Setup the reveal view animation
    239             int width = revealView.getMeasuredWidth();
    240             int height = revealView.getMeasuredHeight();
    241             float revealRadius = (float) Math.hypot(width / 2, height / 2);
    242             revealView.setVisibility(View.VISIBLE);
    243             revealView.setAlpha(0f);
    244             revealView.setTranslationY(0f);
    245             revealView.setTranslationX(0f);
    246 
    247             // Calculate the final animation values
    248             final float revealViewToAlpha;
    249             final float revealViewToXDrift;
    250             final float revealViewToYDrift;
    251             if (material) {
    252                 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
    253                         buttonView, null);
    254                 revealViewToAlpha = pCb.getMaterialRevealViewFinalAlpha(revealView);
    255                 revealViewToYDrift = buttonViewToPanelDelta[1];
    256                 revealViewToXDrift = buttonViewToPanelDelta[0];
    257             } else {
    258                 revealViewToAlpha = 0f;
    259                 revealViewToYDrift = 2 * height / 3;
    260                 revealViewToXDrift = 0;
    261             }
    262 
    263             // Create the animators
    264             PropertyValuesHolder panelAlpha =
    265                     PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f);
    266             PropertyValuesHolder panelDriftY =
    267                     PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0);
    268             PropertyValuesHolder panelDriftX =
    269                     PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0);
    270             ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
    271                     panelAlpha, panelDriftY, panelDriftX);
    272             panelAlphaAndDrift.setDuration(revealDuration);
    273             panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
    274 
    275             // Play the animation
    276             layerViews.put(revealView, BUILD_AND_SET_LAYER);
    277             mStateAnimation.play(panelAlphaAndDrift);
    278 
    279             if (overlaySearchBarView != null) {
    280                 overlaySearchBarView.setAlpha(0f);
    281                 ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 0f, 1f);
    282                 searchBarAlpha.setDuration(100);
    283                 searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
    284                 layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
    285                 mStateAnimation.play(searchBarAlpha);
    286             }
    287 
    288             // Setup the animation for the content view
    289             contentView.setVisibility(View.VISIBLE);
    290             contentView.setAlpha(0f);
    291             contentView.setTranslationY(revealViewToYDrift);
    292             layerViews.put(contentView, BUILD_AND_SET_LAYER);
    293 
    294             // Create the individual animators
    295             ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
    296                     revealViewToYDrift, 0);
    297             pageDrift.setDuration(revealDuration);
    298             pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
    299             pageDrift.setStartDelay(itemsAlphaStagger);
    300             mStateAnimation.play(pageDrift);
    301 
    302             ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
    303             itemsAlpha.setDuration(revealDuration);
    304             itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
    305             itemsAlpha.setStartDelay(itemsAlphaStagger);
    306             mStateAnimation.play(itemsAlpha);
    307 
    308             if (material) {
    309                 float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
    310                 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
    311                         revealView, buttonView);
    312                 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2,
    313                         height / 2, startRadius, revealRadius);
    314                 reveal.setDuration(revealDuration);
    315                 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
    316                 if (listener != null) {
    317                     reveal.addListener(listener);
    318                 }
    319                 mStateAnimation.play(reveal);
    320             }
    321 
    322             mStateAnimation.addListener(new AnimatorListenerAdapter() {
    323                 @Override
    324                 public void onAnimationEnd(Animator animation) {
    325                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
    326                     dispatchOnLauncherTransitionEnd(toView, animated, false);
    327 
    328                     // Hide the reveal view
    329                     revealView.setVisibility(View.INVISIBLE);
    330 
    331                     // Disable all necessary layers
    332                     for (View v : layerViews.keySet()) {
    333                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    334                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    335                         }
    336                     }
    337 
    338                     if (hideSearchBar) {
    339                         mCb.onStateTransitionHideSearchBar();
    340                     }
    341 
    342                     // This can hold unnecessary references to views.
    343                     mStateAnimation = null;
    344                     pCb.onTransitionComplete();
    345                 }
    346 
    347             });
    348 
    349             // Play the workspace animation
    350             if (workspaceAnim != null) {
    351                 mStateAnimation.play(workspaceAnim);
    352             }
    353 
    354             // Dispatch the prepare transition signal
    355             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
    356             dispatchOnLauncherTransitionPrepare(toView, animated, false);
    357 
    358 
    359             final AnimatorSet stateAnimation = mStateAnimation;
    360             final Runnable startAnimRunnable = new Runnable() {
    361                 public void run() {
    362                     // Check that mStateAnimation hasn't changed while
    363                     // we waited for a layout/draw pass
    364                     if (mStateAnimation != stateAnimation)
    365                         return;
    366                     dispatchOnLauncherTransitionStart(fromView, animated, false);
    367                     dispatchOnLauncherTransitionStart(toView, animated, false);
    368 
    369                     // Enable all necessary layers
    370                     boolean isLmpOrAbove = Utilities.isLmpOrAbove();
    371                     for (View v : layerViews.keySet()) {
    372                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    373                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    374                         }
    375                         if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
    376                             v.buildLayer();
    377                         }
    378                     }
    379 
    380                     // Focus the new view
    381                     toView.requestFocus();
    382 
    383                     mStateAnimation.start();
    384                 }
    385             };
    386             toView.bringToFront();
    387             toView.setVisibility(View.VISIBLE);
    388             toView.post(startAnimRunnable);
    389         } else {
    390             toView.setTranslationX(0.0f);
    391             toView.setTranslationY(0.0f);
    392             toView.setScaleX(1.0f);
    393             toView.setScaleY(1.0f);
    394             toView.setVisibility(View.VISIBLE);
    395             toView.bringToFront();
    396 
    397             // Show the content view
    398             contentView.setVisibility(View.VISIBLE);
    399 
    400             if (hideSearchBar) {
    401                 mCb.onStateTransitionHideSearchBar();
    402             }
    403 
    404             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
    405             dispatchOnLauncherTransitionStart(fromView, animated, false);
    406             dispatchOnLauncherTransitionEnd(fromView, animated, false);
    407             dispatchOnLauncherTransitionPrepare(toView, animated, false);
    408             dispatchOnLauncherTransitionStart(toView, animated, false);
    409             dispatchOnLauncherTransitionEnd(toView, animated, false);
    410             pCb.onTransitionComplete();
    411         }
    412     }
    413 
    414     /**
    415      * Starts and animation to the workspace from the apps view.
    416      */
    417     private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
    418             final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
    419         AllAppsContainerView appsView = mLauncher.getAppsView();
    420         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
    421             int[] mAllAppsToPanelDelta;
    422 
    423             @Override
    424             float getMaterialRevealViewFinalAlpha(View revealView) {
    425                 // No alpha anim from all apps
    426                 return 1f;
    427             }
    428             @Override
    429             float getMaterialRevealViewStartFinalRadius() {
    430                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
    431                 return allAppsButtonSize / 2;
    432             }
    433             @Override
    434             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
    435                     final View revealView, final View allAppsButtonView) {
    436                 return new AnimatorListenerAdapter() {
    437                     public void onAnimationStart(Animator animation) {
    438                         // We set the alpha instead of visibility to ensure that the focus does not
    439                         // get taken from the all apps view
    440                         allAppsButtonView.setVisibility(View.VISIBLE);
    441                         allAppsButtonView.setAlpha(0f);
    442                     }
    443                     public void onAnimationEnd(Animator animation) {
    444                         // Hide the reveal view
    445                         revealView.setVisibility(View.INVISIBLE);
    446 
    447                         // Show the all apps button, and focus it
    448                         allAppsButtonView.setAlpha(1f);
    449                     }
    450                 };
    451             }
    452         };
    453         // Only animate the search bar if animating to spring loaded mode from all apps
    454         startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage,
    455                 mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
    456                 appsView.getRevealView(), appsView.getSearchBarView(), animated,
    457                 onCompleteRunnable, cb);
    458     }
    459 
    460     /**
    461      * Starts and animation to the workspace from the widgets view.
    462      */
    463     private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
    464               final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
    465         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
    466         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
    467             @Override
    468             float getMaterialRevealViewFinalAlpha(View revealView) {
    469                 return 0.3f;
    470             }
    471             @Override
    472             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
    473                     final View revealView, final View widgetsButtonView) {
    474                 return new AnimatorListenerAdapter() {
    475                     public void onAnimationEnd(Animator animation) {
    476                         // Hide the reveal view
    477                         revealView.setVisibility(View.INVISIBLE);
    478                     }
    479                 };
    480             }
    481         };
    482         startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage,
    483                 mLauncher.getWidgetsButton(), widgetsView, widgetsView.getContentView(),
    484                 widgetsView.getRevealView(), null, animated, onCompleteRunnable, cb);
    485     }
    486 
    487     /**
    488      * Creates and starts a new animation to the workspace.
    489      */
    490     private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
    491               final int toWorkspacePage, final View buttonView, final View fromView,
    492               final View contentView, final View revealView, final View overlaySearchBarView,
    493               final boolean animated, final Runnable onCompleteRunnable,
    494               final PrivateTransitionCallbacks pCb) {
    495         final Resources res = mLauncher.getResources();
    496         final boolean material = Utilities.isLmpOrAbove();
    497         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
    498         final int itemsAlphaStagger =
    499                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
    500 
    501         final View toView = mLauncher.getWorkspace();
    502 
    503         final HashMap<View, Integer> layerViews = new HashMap<>();
    504 
    505         // If for some reason our views aren't initialized, don't animate
    506         boolean initialized = buttonView != null;
    507 
    508         // Cancel the current animation
    509         cancelAnimation();
    510 
    511         // Create the workspace animation.
    512         // NOTE: this call apparently also sets the state for the workspace if !animated
    513         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
    514                 toWorkspacePage, animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
    515                 layerViews);
    516 
    517         if (animated && initialized) {
    518             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
    519 
    520             // Play the workspace animation
    521             if (workspaceAnim != null) {
    522                 mStateAnimation.play(workspaceAnim);
    523             }
    524 
    525             // hideAppsCustomizeHelper is called in some cases when it is already hidden
    526             // don't perform all these no-op animations. In particularly, this was causing
    527             // the all-apps button to pop in and out.
    528             if (fromView.getVisibility() == View.VISIBLE) {
    529                 int width = revealView.getMeasuredWidth();
    530                 int height = revealView.getMeasuredHeight();
    531                 float revealRadius = (float) Math.hypot(width / 2, height / 2);
    532                 revealView.setVisibility(View.VISIBLE);
    533                 revealView.setAlpha(1f);
    534                 revealView.setTranslationY(0);
    535                 layerViews.put(revealView, BUILD_AND_SET_LAYER);
    536 
    537                 // Calculate the final animation values
    538                 final float revealViewToXDrift;
    539                 final float revealViewToYDrift;
    540                 if (material) {
    541                     int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
    542                             buttonView, null);
    543                     revealViewToYDrift = buttonViewToPanelDelta[1];
    544                     revealViewToXDrift = buttonViewToPanelDelta[0];
    545                 } else {
    546                     revealViewToYDrift = 2 * height / 3;
    547                     revealViewToXDrift = 0;
    548                 }
    549 
    550                 // The vertical motion of the apps panel should be delayed by one frame
    551                 // from the conceal animation in order to give the right feel. We correspondingly
    552                 // shorten the duration so that the slide and conceal end at the same time.
    553                 TimeInterpolator decelerateInterpolator = material ?
    554                         new LogDecelerateInterpolator(100, 0) :
    555                         new DecelerateInterpolator(1f);
    556                 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
    557                         0, revealViewToYDrift);
    558                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
    559                 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
    560                 panelDriftY.setInterpolator(decelerateInterpolator);
    561                 mStateAnimation.play(panelDriftY);
    562 
    563                 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
    564                         0, revealViewToXDrift);
    565                 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
    566                 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
    567                 panelDriftX.setInterpolator(decelerateInterpolator);
    568                 mStateAnimation.play(panelDriftX);
    569 
    570                 // Setup animation for the reveal panel alpha
    571                 final float revealViewToAlpha = !material ? 0f :
    572                         pCb.getMaterialRevealViewFinalAlpha(revealView);
    573                 if (revealViewToAlpha != 1f) {
    574                     ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
    575                             1f, revealViewToAlpha);
    576                     panelAlpha.setDuration(material ? revealDuration : 150);
    577                     panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
    578                     panelAlpha.setInterpolator(decelerateInterpolator);
    579                     mStateAnimation.play(panelAlpha);
    580                 }
    581 
    582                 // Setup the animation for the content view
    583                 layerViews.put(contentView, BUILD_AND_SET_LAYER);
    584 
    585                 // Create the individual animators
    586                 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
    587                         0, revealViewToYDrift);
    588                 contentView.setTranslationY(0);
    589                 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
    590                 pageDrift.setInterpolator(decelerateInterpolator);
    591                 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
    592                 mStateAnimation.play(pageDrift);
    593 
    594                 contentView.setAlpha(1f);
    595                 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
    596                 itemsAlpha.setDuration(100);
    597                 itemsAlpha.setInterpolator(decelerateInterpolator);
    598                 mStateAnimation.play(itemsAlpha);
    599 
    600                 if (overlaySearchBarView != null) {
    601                     overlaySearchBarView.setAlpha(1f);
    602                     ObjectAnimator searchAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 1f, 0f);
    603                     searchAlpha.setDuration(material ? 100 : 150);
    604                     searchAlpha.setInterpolator(decelerateInterpolator);
    605                     searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
    606                     layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
    607                     mStateAnimation.play(searchAlpha);
    608                 }
    609 
    610                 if (material) {
    611                     // Animate the all apps button
    612                     float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
    613                     AnimatorListenerAdapter listener =
    614                             pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
    615                     Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2,
    616                             height / 2, revealRadius, finalRadius);
    617                     reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
    618                     reveal.setDuration(revealDuration);
    619                     reveal.setStartDelay(itemsAlphaStagger);
    620                     if (listener != null) {
    621                         reveal.addListener(listener);
    622                     }
    623                     mStateAnimation.play(reveal);
    624                 }
    625 
    626                 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
    627                 dispatchOnLauncherTransitionPrepare(toView, animated, true);
    628             }
    629 
    630             mStateAnimation.addListener(new AnimatorListenerAdapter() {
    631                 @Override
    632                 public void onAnimationEnd(Animator animation) {
    633                     fromView.setVisibility(View.GONE);
    634                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
    635                     dispatchOnLauncherTransitionEnd(toView, animated, true);
    636 
    637                     // Run any queued runnables
    638                     if (onCompleteRunnable != null) {
    639                         onCompleteRunnable.run();
    640                     }
    641 
    642                     // Disable all necessary layers
    643                     for (View v : layerViews.keySet()) {
    644                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    645                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    646                         }
    647                     }
    648 
    649                     // Reset page transforms
    650                     if (contentView != null) {
    651                         contentView.setTranslationX(0);
    652                         contentView.setTranslationY(0);
    653                         contentView.setAlpha(1);
    654                     }
    655                     if (overlaySearchBarView != null) {
    656                         overlaySearchBarView.setAlpha(1f);
    657                     }
    658 
    659                     // This can hold unnecessary references to views.
    660                     mStateAnimation = null;
    661                     pCb.onTransitionComplete();
    662                 }
    663             });
    664 
    665             final AnimatorSet stateAnimation = mStateAnimation;
    666             final Runnable startAnimRunnable = new Runnable() {
    667                 public void run() {
    668                     // Check that mStateAnimation hasn't changed while
    669                     // we waited for a layout/draw pass
    670                     if (mStateAnimation != stateAnimation)
    671                         return;
    672                     dispatchOnLauncherTransitionStart(fromView, animated, false);
    673                     dispatchOnLauncherTransitionStart(toView, animated, false);
    674 
    675                     // Enable all necessary layers
    676                     boolean isLmpOrAbove = Utilities.isLmpOrAbove();
    677                     for (View v : layerViews.keySet()) {
    678                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    679                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    680                         }
    681                         if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
    682                             v.buildLayer();
    683                         }
    684                     }
    685                     mStateAnimation.start();
    686                 }
    687             };
    688             fromView.post(startAnimRunnable);
    689         } else {
    690             fromView.setVisibility(View.GONE);
    691             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
    692             dispatchOnLauncherTransitionStart(fromView, animated, true);
    693             dispatchOnLauncherTransitionEnd(fromView, animated, true);
    694             dispatchOnLauncherTransitionPrepare(toView, animated, true);
    695             dispatchOnLauncherTransitionStart(toView, animated, true);
    696             dispatchOnLauncherTransitionEnd(toView, animated, true);
    697             pCb.onTransitionComplete();
    698 
    699             // Run any queued runnables
    700             if (onCompleteRunnable != null) {
    701                 onCompleteRunnable.run();
    702             }
    703         }
    704     }
    705 
    706 
    707     /**
    708      * Dispatches the prepare-transition event to suitable views.
    709      */
    710     void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
    711         if (v instanceof LauncherTransitionable) {
    712             ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
    713                     toWorkspace);
    714         }
    715     }
    716 
    717     /**
    718      * Dispatches the start-transition event to suitable views.
    719      */
    720     void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
    721         if (v instanceof LauncherTransitionable) {
    722             ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
    723                     toWorkspace);
    724         }
    725 
    726         // Update the workspace transition step as well
    727         dispatchOnLauncherTransitionStep(v, 0f);
    728     }
    729 
    730     /**
    731      * Dispatches the step-transition event to suitable views.
    732      */
    733     void dispatchOnLauncherTransitionStep(View v, float t) {
    734         if (v instanceof LauncherTransitionable) {
    735             ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
    736         }
    737     }
    738 
    739     /**
    740      * Dispatches the end-transition event to suitable views.
    741      */
    742     void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
    743         if (v instanceof LauncherTransitionable) {
    744             ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
    745                     toWorkspace);
    746         }
    747 
    748         // Update the workspace transition step as well
    749         dispatchOnLauncherTransitionStep(v, 1f);
    750     }
    751 
    752     /**
    753      * Cancels the current animation.
    754      */
    755     private void cancelAnimation() {
    756         if (mStateAnimation != null) {
    757             mStateAnimation.setDuration(0);
    758             mStateAnimation.cancel();
    759             mStateAnimation = null;
    760         }
    761     }
    762 }
    763