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.annotation.TargetApi;
     28 import android.content.res.Resources;
     29 import android.os.Build;
     30 import android.util.Log;
     31 import android.view.View;
     32 import android.view.animation.AccelerateInterpolator;
     33 import android.view.animation.DecelerateInterpolator;
     34 
     35 import com.android.launcher3.allapps.AllAppsContainerView;
     36 import com.android.launcher3.allapps.AllAppsTransitionController;
     37 import com.android.launcher3.config.FeatureFlags;
     38 import com.android.launcher3.util.CircleRevealOutlineProvider;
     39 import com.android.launcher3.util.Thunk;
     40 import com.android.launcher3.widget.WidgetsContainerView;
     41 
     42 import java.util.HashMap;
     43 
     44 /**
     45  * TODO: figure out what kind of tests we can write for this
     46  *
     47  * Things to test when changing the following class.
     48  *   - Home from workspace
     49  *          - from center screen
     50  *          - from other screens
     51  *   - Home from all apps
     52  *          - from center screen
     53  *          - from other screens
     54  *   - Back from all apps
     55  *          - from center screen
     56  *          - from other screens
     57  *   - Launch app from workspace and quit
     58  *          - with back
     59  *          - with home
     60  *   - Launch app from all apps and quit
     61  *          - with back
     62  *          - with home
     63  *   - Go to a screen that's not the default, then all
     64  *     apps, and launch and app, and go back
     65  *          - with back
     66  *          -with home
     67  *   - On workspace, long press power and go back
     68  *          - with back
     69  *          - with home
     70  *   - On all apps, long press power and go back
     71  *          - with back
     72  *          - with home
     73  *   - On workspace, power off
     74  *   - On all apps, power off
     75  *   - Launch an app and turn off the screen while in that app
     76  *          - Go back with home key
     77  *          - Go back with back key  TODO: make this not go to workspace
     78  *          - From all apps
     79  *          - From workspace
     80  *   - Enter and exit car mode (becuase it causes an extra configuration changed)
     81  *          - From all apps
     82  *          - From the center workspace
     83  *          - From another workspace
     84  */
     85 public class LauncherStateTransitionAnimation {
     86 
     87     /**
     88      * animation used for all apps and widget tray when
     89      *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
     90      */
     91     public static final int CIRCULAR_REVEAL = 0;
     92     /**
     93      * animation used for all apps and not widget tray when
     94      *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
     95      */
     96     public static final int PULLUP = 1;
     97 
     98     private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
     99 
    100     /**
    101      * Private callbacks made during transition setup.
    102      */
    103     private static class PrivateTransitionCallbacks {
    104         private final float materialRevealViewFinalAlpha;
    105 
    106         PrivateTransitionCallbacks(float revealAlpha) {
    107             materialRevealViewFinalAlpha = revealAlpha;
    108         }
    109 
    110         float getMaterialRevealViewStartFinalRadius() {
    111             return 0;
    112         }
    113         AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
    114                 View buttonView) {
    115             return null;
    116         }
    117         void onTransitionComplete() {}
    118     }
    119 
    120     public static final String TAG = "LSTAnimation";
    121 
    122     // Flags to determine how to set the layers on views before the transition animation
    123     public static final int BUILD_LAYER = 0;
    124     public static final int BUILD_AND_SET_LAYER = 1;
    125     public static final int SINGLE_FRAME_DELAY = 16;
    126 
    127     @Thunk Launcher mLauncher;
    128     @Thunk AnimatorSet mCurrentAnimation;
    129     AllAppsTransitionController mAllAppsController;
    130 
    131     public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
    132         mLauncher = l;
    133         mAllAppsController = allAppsController;
    134     }
    135 
    136     /**
    137      * Starts an animation to the apps view.
    138      *
    139      * @param startSearchAfterTransition Immediately starts app search after the transition to
    140      *                                   All Apps is completed.
    141      */
    142     public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
    143             final boolean animated, final boolean startSearchAfterTransition) {
    144         final AllAppsContainerView toView = mLauncher.getAppsView();
    145         final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
    146         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
    147             @Override
    148             public float getMaterialRevealViewStartFinalRadius() {
    149                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
    150                 return allAppsButtonSize / 2;
    151             }
    152             @Override
    153             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
    154                     final View revealView, final View allAppsButtonView) {
    155                 return new AnimatorListenerAdapter() {
    156                     public void onAnimationStart(Animator animation) {
    157                         allAppsButtonView.setVisibility(View.INVISIBLE);
    158                     }
    159                     public void onAnimationEnd(Animator animation) {
    160                         allAppsButtonView.setVisibility(View.VISIBLE);
    161                     }
    162                 };
    163             }
    164             @Override
    165             void onTransitionComplete() {
    166                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
    167                 if (startSearchAfterTransition) {
    168                     toView.startAppsSearch();
    169                 }
    170             }
    171         };
    172         int animType = CIRCULAR_REVEAL;
    173         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
    174             animType = PULLUP;
    175         }
    176         // Only animate the search bar if animating from spring loaded mode back to all apps
    177         startAnimationToOverlay(fromWorkspaceState,
    178                 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
    179     }
    180 
    181     /**
    182      * Starts an animation to the widgets view.
    183      */
    184     public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
    185             final boolean animated) {
    186         final WidgetsContainerView toView = mLauncher.getWidgetsView();
    187         final View buttonView = mLauncher.getWidgetsButton();
    188         startAnimationToOverlay(fromWorkspaceState,
    189                 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
    190                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
    191                     @Override
    192                     void onTransitionComplete() {
    193                         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
    194                     }
    195                 });
    196     }
    197 
    198     /**
    199      * Starts an animation to the workspace from the current overlay view.
    200      */
    201     public void startAnimationToWorkspace(final Launcher.State fromState,
    202             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
    203             final boolean animated, final Runnable onCompleteRunnable) {
    204         if (toWorkspaceState != Workspace.State.NORMAL &&
    205                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
    206                 toWorkspaceState != Workspace.State.OVERVIEW) {
    207             Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
    208         }
    209 
    210         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
    211                 || mAllAppsController.isTransitioning()) {
    212             int animType = CIRCULAR_REVEAL;
    213             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
    214                 animType = PULLUP;
    215             }
    216             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
    217                     animated, animType, onCompleteRunnable);
    218         } else if (fromState == Launcher.State.WIDGETS ||
    219                 fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
    220             startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
    221                     animated, onCompleteRunnable);
    222         } else {
    223             startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
    224                     animated, onCompleteRunnable);
    225         }
    226     }
    227 
    228     /**
    229      * Creates and starts a new animation to a particular overlay view.
    230      */
    231     @SuppressLint("NewApi")
    232     private void startAnimationToOverlay(
    233             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
    234             final View buttonView, final BaseContainerView toView,
    235             final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
    236         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
    237         final Resources res = mLauncher.getResources();
    238         final boolean material = Utilities.ATLEAST_LOLLIPOP;
    239         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
    240         final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
    241 
    242         final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
    243 
    244         final View fromView = mLauncher.getWorkspace();
    245 
    246         final HashMap<View, Integer> layerViews = new HashMap<>();
    247 
    248         // If for some reason our views aren't initialized, don't animate
    249         boolean initialized = buttonView != null;
    250 
    251         // Cancel the current animation
    252         cancelAnimation();
    253 
    254         final View contentView = toView.getContentView();
    255         playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
    256                 animated, initialized, animation, layerViews);
    257         if (!animated || !initialized) {
    258             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
    259                     toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
    260                 mAllAppsController.finishPullUp();
    261             }
    262             toView.setTranslationX(0.0f);
    263             toView.setTranslationY(0.0f);
    264             toView.setScaleX(1.0f);
    265             toView.setScaleY(1.0f);
    266             toView.setAlpha(1.0f);
    267             toView.setVisibility(View.VISIBLE);
    268 
    269             // Show the content view
    270             contentView.setVisibility(View.VISIBLE);
    271 
    272             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
    273             dispatchOnLauncherTransitionStart(fromView, animated, false);
    274             dispatchOnLauncherTransitionEnd(fromView, animated, false);
    275             dispatchOnLauncherTransitionPrepare(toView, animated, false);
    276             dispatchOnLauncherTransitionStart(toView, animated, false);
    277             dispatchOnLauncherTransitionEnd(toView, animated, false);
    278             pCb.onTransitionComplete();
    279             return;
    280         }
    281         if (animType == CIRCULAR_REVEAL) {
    282             // Setup the reveal view animation
    283             final View revealView = toView.getRevealView();
    284 
    285             int width = revealView.getMeasuredWidth();
    286             int height = revealView.getMeasuredHeight();
    287             float revealRadius = (float) Math.hypot(width / 2, height / 2);
    288             revealView.setVisibility(View.VISIBLE);
    289             revealView.setAlpha(0f);
    290             revealView.setTranslationY(0f);
    291             revealView.setTranslationX(0f);
    292 
    293             // Calculate the final animation values
    294             final float revealViewToAlpha;
    295             final float revealViewToXDrift;
    296             final float revealViewToYDrift;
    297             if (material) {
    298                 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
    299                         revealView, buttonView, null);
    300                 revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
    301                 revealViewToYDrift = buttonViewToPanelDelta[1];
    302                 revealViewToXDrift = buttonViewToPanelDelta[0];
    303             } else {
    304                 revealViewToAlpha = 0f;
    305                 revealViewToYDrift = 2 * height / 3;
    306                 revealViewToXDrift = 0;
    307             }
    308 
    309             // Create the animators
    310             PropertyValuesHolder panelAlpha =
    311                     PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
    312             PropertyValuesHolder panelDriftY =
    313                     PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
    314             PropertyValuesHolder panelDriftX =
    315                     PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
    316             ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
    317                     panelAlpha, panelDriftY, panelDriftX);
    318             panelAlphaAndDrift.setDuration(revealDuration);
    319             panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
    320 
    321             // Play the animation
    322             layerViews.put(revealView, BUILD_AND_SET_LAYER);
    323             animation.play(panelAlphaAndDrift);
    324 
    325             // Setup the animation for the content view
    326             contentView.setVisibility(View.VISIBLE);
    327             contentView.setAlpha(0f);
    328             contentView.setTranslationY(revealViewToYDrift);
    329             layerViews.put(contentView, BUILD_AND_SET_LAYER);
    330 
    331             // Create the individual animators
    332             ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
    333                     revealViewToYDrift, 0);
    334             pageDrift.setDuration(revealDuration);
    335             pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
    336             pageDrift.setStartDelay(itemsAlphaStagger);
    337             animation.play(pageDrift);
    338 
    339             ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
    340             itemsAlpha.setDuration(revealDuration);
    341             itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
    342             itemsAlpha.setStartDelay(itemsAlphaStagger);
    343             animation.play(itemsAlpha);
    344 
    345             if (material) {
    346                 float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
    347                 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
    348                         revealView, buttonView);
    349                 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
    350                         startRadius, revealRadius).createRevealAnimator(revealView);
    351                 reveal.setDuration(revealDuration);
    352                 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
    353                 if (listener != null) {
    354                     reveal.addListener(listener);
    355                 }
    356                 animation.play(reveal);
    357             }
    358 
    359             animation.addListener(new AnimatorListenerAdapter() {
    360                 @Override
    361                 public void onAnimationEnd(Animator animation) {
    362                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
    363                     dispatchOnLauncherTransitionEnd(toView, animated, false);
    364 
    365                     // Hide the reveal view
    366                     revealView.setVisibility(View.INVISIBLE);
    367 
    368                     // Disable all necessary layers
    369                     for (View v : layerViews.keySet()) {
    370                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    371                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    372                         }
    373                     }
    374 
    375                     // This can hold unnecessary references to views.
    376                     cleanupAnimation();
    377                     pCb.onTransitionComplete();
    378                 }
    379 
    380             });
    381 
    382             // Dispatch the prepare transition signal
    383             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
    384             dispatchOnLauncherTransitionPrepare(toView, animated, false);
    385 
    386             final AnimatorSet stateAnimation = animation;
    387             final Runnable startAnimRunnable = new Runnable() {
    388                 public void run() {
    389                     // Check that mCurrentAnimation hasn't changed while
    390                     // we waited for a layout/draw pass
    391                     if (mCurrentAnimation != stateAnimation)
    392                         return;
    393                     dispatchOnLauncherTransitionStart(fromView, animated, false);
    394                     dispatchOnLauncherTransitionStart(toView, animated, false);
    395 
    396                     // Enable all necessary layers
    397                     for (View v : layerViews.keySet()) {
    398                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    399                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    400                         }
    401                         if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
    402                             v.buildLayer();
    403                         }
    404                     }
    405 
    406                     // Focus the new view
    407                     toView.requestFocus();
    408 
    409                     stateAnimation.start();
    410                 }
    411             };
    412             toView.bringToFront();
    413             toView.setVisibility(View.VISIBLE);
    414             toView.post(startAnimRunnable);
    415             mCurrentAnimation = animation;
    416         } else if (animType == PULLUP) {
    417             // We are animating the content view alpha, so ensure we have a layer for it
    418             layerViews.put(contentView, BUILD_AND_SET_LAYER);
    419 
    420             animation.addListener(new AnimatorListenerAdapter() {
    421                 @Override
    422                 public void onAnimationEnd(Animator animation) {
    423                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
    424                     dispatchOnLauncherTransitionEnd(toView, animated, false);
    425 
    426                     // Disable all necessary layers
    427                     for (View v : layerViews.keySet()) {
    428                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    429                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    430                         }
    431                     }
    432 
    433                     cleanupAnimation();
    434                     pCb.onTransitionComplete();
    435                 }
    436             });
    437             boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
    438 
    439             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
    440             dispatchOnLauncherTransitionPrepare(toView, animated, false);
    441 
    442             final AnimatorSet stateAnimation = animation;
    443             final Runnable startAnimRunnable = new Runnable() {
    444                 public void run() {
    445                     // Check that mCurrentAnimation hasn't changed while
    446                     // we waited for a layout/draw pass
    447                     if (mCurrentAnimation != stateAnimation)
    448                         return;
    449 
    450                     dispatchOnLauncherTransitionStart(fromView, animated, false);
    451                     dispatchOnLauncherTransitionStart(toView, animated, false);
    452 
    453                     // Enable all necessary layers
    454                     for (View v : layerViews.keySet()) {
    455                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    456                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    457                         }
    458                         if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
    459                             v.buildLayer();
    460                         }
    461                     }
    462 
    463                     toView.requestFocus();
    464                     stateAnimation.start();
    465                 }
    466             };
    467             mCurrentAnimation = animation;
    468             if (shouldPost) {
    469                 toView.post(startAnimRunnable);
    470             } else {
    471                 startAnimRunnable.run();
    472             }
    473         }
    474     }
    475 
    476     /**
    477      * Plays animations used by various transitions.
    478      */
    479     private void playCommonTransitionAnimations(
    480             Workspace.State toWorkspaceState, View fromView, View toView,
    481             boolean animated, boolean initialized, AnimatorSet animation,
    482             HashMap<View, Integer> layerViews) {
    483         // Create the workspace animation.
    484         // NOTE: this call apparently also sets the state for the workspace if !animated
    485         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
    486                 animated, layerViews);
    487 
    488         if (animated && initialized) {
    489             // Play the workspace animation
    490             if (workspaceAnim != null) {
    491                 animation.play(workspaceAnim);
    492             }
    493             // Dispatch onLauncherTransitionStep() as the animation interpolates.
    494             animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
    495         }
    496     }
    497 
    498     /**
    499      * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
    500      * {@param fromView} and {@param toView} as the animation interpolates.
    501      *
    502      * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
    503      */
    504     private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
    505         ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
    506         updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    507             @Override
    508             public void onAnimationUpdate(ValueAnimator animation) {
    509                 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
    510                 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
    511             }
    512         });
    513         return updateAnimator;
    514     }
    515 
    516     /**
    517      * Starts an animation to the workspace from the apps view.
    518      */
    519     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
    520             final Workspace.State toWorkspaceState, final boolean animated, int type,
    521             final Runnable onCompleteRunnable) {
    522         AllAppsContainerView appsView = mLauncher.getAppsView();
    523         // No alpha anim from all apps
    524         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
    525             @Override
    526             float getMaterialRevealViewStartFinalRadius() {
    527                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
    528                 return allAppsButtonSize / 2;
    529             }
    530             @Override
    531             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
    532                     final View revealView, final View allAppsButtonView) {
    533                 return new AnimatorListenerAdapter() {
    534                     public void onAnimationStart(Animator animation) {
    535                         // We set the alpha instead of visibility to ensure that the focus does not
    536                         // get taken from the all apps view
    537                         allAppsButtonView.setVisibility(View.VISIBLE);
    538                         allAppsButtonView.setAlpha(0f);
    539                     }
    540                     public void onAnimationEnd(Animator animation) {
    541                         // Hide the reveal view
    542                         revealView.setVisibility(View.INVISIBLE);
    543 
    544                         // Show the all apps button, and focus it
    545                         allAppsButtonView.setAlpha(1f);
    546                     }
    547                 };
    548             }
    549             @Override
    550             void onTransitionComplete() {
    551                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
    552             }
    553         };
    554         // Only animate the search bar if animating to spring loaded mode from all apps
    555         startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
    556                 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
    557                 animated, type, onCompleteRunnable, cb);
    558     }
    559 
    560     /**
    561      * Starts an animation to the workspace from the widgets view.
    562      */
    563     private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
    564             final Workspace.State toWorkspaceState, final boolean animated,
    565             final Runnable onCompleteRunnable) {
    566         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
    567         PrivateTransitionCallbacks cb =
    568                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
    569             @Override
    570             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
    571                     final View revealView, final View widgetsButtonView) {
    572                 return new AnimatorListenerAdapter() {
    573                     public void onAnimationEnd(Animator animation) {
    574                         // Hide the reveal view
    575                         revealView.setVisibility(View.INVISIBLE);
    576                     }
    577                 };
    578             }
    579             @Override
    580             void onTransitionComplete() {
    581                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
    582             }
    583         };
    584         startAnimationToWorkspaceFromOverlay(
    585                 fromWorkspaceState, toWorkspaceState,
    586                 mLauncher.getWidgetsButton(), widgetsView,
    587                 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
    588     }
    589 
    590     /**
    591      * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
    592      */
    593     private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
    594             final Workspace.State toWorkspaceState, final boolean animated,
    595             final Runnable onCompleteRunnable) {
    596         final View fromWorkspace = mLauncher.getWorkspace();
    597         final HashMap<View, Integer> layerViews = new HashMap<>();
    598         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
    599         final int revealDuration = mLauncher.getResources()
    600                 .getInteger(R.integer.config_overlayRevealTime);
    601 
    602         // Cancel the current animation
    603         cancelAnimation();
    604 
    605         boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
    606 
    607         playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
    608                 animated, animated, animation, layerViews);
    609 
    610         if (animated) {
    611             dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
    612 
    613             final AnimatorSet stateAnimation = animation;
    614             final Runnable startAnimRunnable = new Runnable() {
    615                 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    616                 public void run() {
    617                     // Check that mCurrentAnimation hasn't changed while
    618                     // we waited for a layout/draw pass
    619                     if (mCurrentAnimation != stateAnimation)
    620                         return;
    621 
    622                     dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
    623 
    624                     // Enable all necessary layers
    625                     for (View v : layerViews.keySet()) {
    626                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    627                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    628                         }
    629                         if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
    630                             v.buildLayer();
    631                         }
    632                     }
    633                     stateAnimation.start();
    634                 }
    635             };
    636             animation.addListener(new AnimatorListenerAdapter() {
    637                 @Override
    638                 public void onAnimationEnd(Animator animation) {
    639                     dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
    640 
    641                     // Run any queued runnables
    642                     if (onCompleteRunnable != null) {
    643                         onCompleteRunnable.run();
    644                     }
    645 
    646                     // Disable all necessary layers
    647                     for (View v : layerViews.keySet()) {
    648                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    649                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    650                         }
    651                     }
    652 
    653                     // This can hold unnecessary references to views.
    654                     cleanupAnimation();
    655                 }
    656             });
    657             fromWorkspace.post(startAnimRunnable);
    658             mCurrentAnimation = animation;
    659         } else /* if (!animated) */ {
    660             dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
    661             dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
    662             dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
    663 
    664             // Run any queued runnables
    665             if (onCompleteRunnable != null) {
    666                 onCompleteRunnable.run();
    667             }
    668 
    669             mCurrentAnimation = null;
    670         }
    671     }
    672 
    673     /**
    674      * Creates and starts a new animation to the workspace.
    675      */
    676     private void startAnimationToWorkspaceFromOverlay(
    677             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
    678             final View buttonView, final BaseContainerView fromView,
    679             final boolean animated, int animType, final Runnable onCompleteRunnable,
    680             final PrivateTransitionCallbacks pCb) {
    681         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
    682         final Resources res = mLauncher.getResources();
    683         final boolean material = Utilities.ATLEAST_LOLLIPOP;
    684         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
    685         final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
    686         final int itemsAlphaStagger =
    687                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
    688 
    689         final View toView = mLauncher.getWorkspace();
    690         final View revealView = fromView.getRevealView();
    691         final View contentView = fromView.getContentView();
    692 
    693         final HashMap<View, Integer> layerViews = new HashMap<>();
    694 
    695         // If for some reason our views aren't initialized, don't animate
    696         boolean initialized = buttonView != null;
    697 
    698         // Cancel the current animation
    699         cancelAnimation();
    700 
    701         boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
    702 
    703         playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
    704                 animated, initialized, animation, layerViews);
    705         if (!animated || !initialized) {
    706             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
    707                     fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
    708                 mAllAppsController.finishPullDown();
    709             }
    710             fromView.setVisibility(View.GONE);
    711             dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
    712             dispatchOnLauncherTransitionStart(fromView, animated, true);
    713             dispatchOnLauncherTransitionEnd(fromView, animated, true);
    714             dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
    715             dispatchOnLauncherTransitionStart(toView, animated, true);
    716             dispatchOnLauncherTransitionEnd(toView, animated, true);
    717             pCb.onTransitionComplete();
    718 
    719             // Run any queued runnables
    720             if (onCompleteRunnable != null) {
    721                 onCompleteRunnable.run();
    722             }
    723             return;
    724         }
    725         if (animType == CIRCULAR_REVEAL) {
    726             // hideAppsCustomizeHelper is called in some cases when it is already hidden
    727             // don't perform all these no-op animations. In particularly, this was causing
    728             // the all-apps button to pop in and out.
    729             if (fromView.getVisibility() == View.VISIBLE) {
    730                 int width = revealView.getMeasuredWidth();
    731                 int height = revealView.getMeasuredHeight();
    732                 float revealRadius = (float) Math.hypot(width / 2, height / 2);
    733                 revealView.setVisibility(View.VISIBLE);
    734                 revealView.setAlpha(1f);
    735                 revealView.setTranslationY(0);
    736                 layerViews.put(revealView, BUILD_AND_SET_LAYER);
    737 
    738                 // Calculate the final animation values
    739                 final float revealViewToXDrift;
    740                 final float revealViewToYDrift;
    741                 if (material) {
    742                     int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
    743                             buttonView, null);
    744                     revealViewToYDrift = buttonViewToPanelDelta[1];
    745                     revealViewToXDrift = buttonViewToPanelDelta[0];
    746                 } else {
    747                     revealViewToYDrift = 2 * height / 3;
    748                     revealViewToXDrift = 0;
    749                 }
    750 
    751                 // The vertical motion of the apps panel should be delayed by one frame
    752                 // from the conceal animation in order to give the right feel. We correspondingly
    753                 // shorten the duration so that the slide and conceal end at the same time.
    754                 TimeInterpolator decelerateInterpolator = material ?
    755                         new LogDecelerateInterpolator(100, 0) :
    756                         new DecelerateInterpolator(1f);
    757                 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
    758                         0, revealViewToYDrift);
    759                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
    760                 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
    761                 panelDriftY.setInterpolator(decelerateInterpolator);
    762                 animation.play(panelDriftY);
    763 
    764                 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
    765                         0, revealViewToXDrift);
    766                 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
    767                 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
    768                 panelDriftX.setInterpolator(decelerateInterpolator);
    769                 animation.play(panelDriftX);
    770 
    771                 // Setup animation for the reveal panel alpha
    772                 final float revealViewToAlpha = !material ? 0f :
    773                         pCb.materialRevealViewFinalAlpha;
    774                 if (revealViewToAlpha != 1f) {
    775                     ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
    776                             1f, revealViewToAlpha);
    777                     panelAlpha.setDuration(material ? revealDuration : 150);
    778                     panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
    779                     panelAlpha.setInterpolator(decelerateInterpolator);
    780                     animation.play(panelAlpha);
    781                 }
    782 
    783                 // Setup the animation for the content view
    784                 layerViews.put(contentView, BUILD_AND_SET_LAYER);
    785 
    786                 // Create the individual animators
    787                 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
    788                         0, revealViewToYDrift);
    789                 contentView.setTranslationY(0);
    790                 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
    791                 pageDrift.setInterpolator(decelerateInterpolator);
    792                 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
    793                 animation.play(pageDrift);
    794 
    795                 contentView.setAlpha(1f);
    796                 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
    797                 itemsAlpha.setDuration(100);
    798                 itemsAlpha.setInterpolator(decelerateInterpolator);
    799                 animation.play(itemsAlpha);
    800 
    801                 // Invalidate the scrim throughout the animation to ensure the highlight
    802                 // cutout is correct throughout.
    803                 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
    804                 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    805                     @Override
    806                     public void onAnimationUpdate(ValueAnimator animation) {
    807                         mLauncher.getDragLayer().invalidateScrim();
    808                     }
    809                 });
    810                 animation.play(invalidateScrim);
    811 
    812                 if (material) {
    813                     // Animate the all apps button
    814                     float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
    815                     AnimatorListenerAdapter listener =
    816                             pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
    817                     Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
    818                             revealRadius, finalRadius).createRevealAnimator(revealView);
    819                     reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
    820                     reveal.setDuration(revealDuration);
    821                     reveal.setStartDelay(itemsAlphaStagger);
    822                     if (listener != null) {
    823                         reveal.addListener(listener);
    824                     }
    825                     animation.play(reveal);
    826                 }
    827             }
    828 
    829             dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
    830             dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
    831 
    832             animation.addListener(new AnimatorListenerAdapter() {
    833                 @Override
    834                 public void onAnimationEnd(Animator animation) {
    835                     fromView.setVisibility(View.GONE);
    836                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
    837                     dispatchOnLauncherTransitionEnd(toView, animated, true);
    838 
    839                     // Run any queued runnables
    840                     if (onCompleteRunnable != null) {
    841                         onCompleteRunnable.run();
    842                     }
    843 
    844                     // Disable all necessary layers
    845                     for (View v : layerViews.keySet()) {
    846                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    847                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    848                         }
    849                     }
    850 
    851                     // Reset page transforms
    852                     if (contentView != null) {
    853                         contentView.setTranslationX(0);
    854                         contentView.setTranslationY(0);
    855                         contentView.setAlpha(1);
    856                     }
    857 
    858                     // This can hold unnecessary references to views.
    859                     cleanupAnimation();
    860                     pCb.onTransitionComplete();
    861                 }
    862             });
    863 
    864             final AnimatorSet stateAnimation = animation;
    865             final Runnable startAnimRunnable = new Runnable() {
    866                 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    867                 public void run() {
    868                     // Check that mCurrentAnimation hasn't changed while
    869                     // we waited for a layout/draw pass
    870                     if (mCurrentAnimation != stateAnimation)
    871                         return;
    872 
    873                     dispatchOnLauncherTransitionStart(fromView, animated, false);
    874                     dispatchOnLauncherTransitionStart(toView, animated, false);
    875 
    876                     // Enable all necessary layers
    877                     for (View v : layerViews.keySet()) {
    878                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    879                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    880                         }
    881                         if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
    882                             v.buildLayer();
    883                         }
    884                     }
    885                     stateAnimation.start();
    886                 }
    887             };
    888             mCurrentAnimation = animation;
    889             fromView.post(startAnimRunnable);
    890         } else if (animType == PULLUP) {
    891             // We are animating the content view alpha, so ensure we have a layer for it
    892             layerViews.put(contentView, BUILD_AND_SET_LAYER);
    893 
    894             animation.addListener(new AnimatorListenerAdapter() {
    895                 boolean canceled = false;
    896                 @Override
    897                 public void onAnimationCancel(Animator animation) {
    898                     canceled = true;
    899                 }
    900 
    901                 @Override
    902                 public void onAnimationEnd(Animator animation) {
    903                     if (canceled) return;
    904                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
    905                     dispatchOnLauncherTransitionEnd(toView, animated, true);
    906                     // Run any queued runnables
    907                     if (onCompleteRunnable != null) {
    908                         onCompleteRunnable.run();
    909                     }
    910 
    911                     // Disable all necessary layers
    912                     for (View v : layerViews.keySet()) {
    913                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    914                             v.setLayerType(View.LAYER_TYPE_NONE, null);
    915                         }
    916                     }
    917 
    918                     cleanupAnimation();
    919                     pCb.onTransitionComplete();
    920                 }
    921 
    922             });
    923             boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
    924 
    925             // Dispatch the prepare transition signal
    926             dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
    927             dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
    928 
    929             final AnimatorSet stateAnimation = animation;
    930             final Runnable startAnimRunnable = new Runnable() {
    931                 public void run() {
    932                     // Check that mCurrentAnimation hasn't changed while
    933                     // we waited for a layout/draw pass
    934                     if (mCurrentAnimation != stateAnimation)
    935                         return;
    936 
    937                     dispatchOnLauncherTransitionStart(fromView, animated, false);
    938                     dispatchOnLauncherTransitionStart(toView, animated, false);
    939 
    940                     // Enable all necessary layers
    941                     for (View v : layerViews.keySet()) {
    942                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
    943                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    944                         }
    945                         if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
    946                             v.buildLayer();
    947                         }
    948                     }
    949 
    950                     // Focus the new view
    951                     toView.requestFocus();
    952                     stateAnimation.start();
    953                 }
    954             };
    955             mCurrentAnimation = animation;
    956             if (shouldPost) {
    957                 fromView.post(startAnimRunnable);
    958             } else {
    959                 startAnimRunnable.run();
    960             }
    961         }
    962         return;
    963     }
    964 
    965     /**
    966      * Dispatches the prepare-transition event to suitable views.
    967      */
    968     void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
    969             boolean multiplePagesVisible) {
    970         if (v instanceof LauncherTransitionable) {
    971             ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
    972                     multiplePagesVisible);
    973         }
    974     }
    975 
    976     /**
    977      * Dispatches the start-transition event to suitable views.
    978      */
    979     void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
    980         if (v instanceof LauncherTransitionable) {
    981             ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
    982                     toWorkspace);
    983         }
    984 
    985         // Update the workspace transition step as well
    986         dispatchOnLauncherTransitionStep(v, 0f);
    987     }
    988 
    989     /**
    990      * Dispatches the step-transition event to suitable views.
    991      */
    992     void dispatchOnLauncherTransitionStep(View v, float t) {
    993         if (v instanceof LauncherTransitionable) {
    994             ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
    995         }
    996     }
    997 
    998     /**
    999      * Dispatches the end-transition event to suitable views.
   1000      */
   1001     void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
   1002         if (v instanceof LauncherTransitionable) {
   1003             ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
   1004                     toWorkspace);
   1005         }
   1006 
   1007         // Update the workspace transition step as well
   1008         dispatchOnLauncherTransitionStep(v, 1f);
   1009     }
   1010 
   1011     /**
   1012      * Cancels the current animation.
   1013      */
   1014     private void cancelAnimation() {
   1015         if (mCurrentAnimation != null) {
   1016             mCurrentAnimation.setDuration(0);
   1017             mCurrentAnimation.cancel();
   1018             mCurrentAnimation = null;
   1019         }
   1020     }
   1021 
   1022     @Thunk void cleanupAnimation() {
   1023         mCurrentAnimation = null;
   1024     }
   1025 }
   1026