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