Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2018 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 static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
     20 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
     21 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
     22 import static com.android.launcher3.LauncherState.ALL_APPS;
     23 import static com.android.launcher3.LauncherState.NORMAL;
     24 import static com.android.launcher3.LauncherState.OVERVIEW;
     25 import static com.android.launcher3.Utilities.postAsyncCallback;
     26 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
     27 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
     28 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
     29 import static com.android.launcher3.anim.Interpolators.LINEAR;
     30 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
     31 import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
     32 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
     33 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
     34 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
     35 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
     36 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
     37 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
     38 
     39 import android.animation.Animator;
     40 import android.animation.AnimatorListenerAdapter;
     41 import android.animation.AnimatorSet;
     42 import android.animation.ObjectAnimator;
     43 import android.animation.ValueAnimator;
     44 import android.annotation.TargetApi;
     45 import android.app.ActivityOptions;
     46 import android.content.Context;
     47 import android.content.pm.PackageManager;
     48 import android.content.res.Resources;
     49 import android.graphics.Matrix;
     50 import android.graphics.Rect;
     51 import android.graphics.drawable.Drawable;
     52 import android.os.Build;
     53 import android.os.CancellationSignal;
     54 import android.os.Handler;
     55 import android.os.Looper;
     56 import android.util.Log;
     57 import android.util.Pair;
     58 import android.view.Surface;
     59 import android.view.View;
     60 import android.view.ViewGroup;
     61 
     62 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
     63 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
     64 import com.android.launcher3.allapps.AllAppsTransitionController;
     65 import com.android.launcher3.anim.AnimatorPlaybackController;
     66 import com.android.launcher3.anim.Interpolators;
     67 import com.android.launcher3.dragndrop.DragLayer;
     68 import com.android.launcher3.graphics.DrawableFactory;
     69 import com.android.launcher3.shortcuts.DeepShortcutView;
     70 import com.android.launcher3.util.MultiValueAlpha;
     71 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
     72 import com.android.quickstep.util.ClipAnimationHelper;
     73 import com.android.quickstep.util.MultiValueUpdateListener;
     74 import com.android.quickstep.util.RemoteAnimationProvider;
     75 import com.android.quickstep.views.RecentsView;
     76 import com.android.quickstep.views.TaskView;
     77 import com.android.systemui.shared.system.ActivityCompat;
     78 import com.android.systemui.shared.system.ActivityOptionsCompat;
     79 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
     80 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
     81 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
     82 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
     83 import com.android.systemui.shared.system.TransactionCompat;
     84 import com.android.systemui.shared.system.WindowManagerWrapper;
     85 
     86 /**
     87  * Manages the opening and closing app transitions from Launcher.
     88  */
     89 @TargetApi(Build.VERSION_CODES.O)
     90 @SuppressWarnings("unused")
     91 public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager
     92         implements OnDeviceProfileChangeListener {
     93 
     94     private static final String TAG = "LauncherTransition";
     95     public static final int STATUS_BAR_TRANSITION_DURATION = 120;
     96 
     97     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
     98             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
     99 
    100     private static final int APP_LAUNCH_DURATION = 500;
    101     // Use a shorter duration for x or y translation to create a curve effect
    102     private static final int APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2;
    103     // We scale the durations for the downward app launch animations (minus the scale animation).
    104     private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
    105     private static final int APP_LAUNCH_ALPHA_START_DELAY = 32;
    106     private static final int APP_LAUNCH_ALPHA_DURATION = 50;
    107 
    108     public static final int RECENTS_LAUNCH_DURATION = 336;
    109     public static final int RECENTS_QUICKSCRUB_LAUNCH_DURATION = 300;
    110     private static final int LAUNCHER_RESUME_START_DELAY = 100;
    111     private static final int CLOSING_TRANSITION_DURATION_MS = 250;
    112 
    113     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
    114     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
    115 
    116     private final Launcher mLauncher;
    117     private final DragLayer mDragLayer;
    118     private final AlphaProperty mDragLayerAlpha;
    119 
    120     private final Handler mHandler;
    121     private final boolean mIsRtl;
    122 
    123     private final float mContentTransY;
    124     private final float mWorkspaceTransY;
    125     private final float mClosingWindowTransY;
    126 
    127     private DeviceProfile mDeviceProfile;
    128     private View mFloatingView;
    129 
    130     private RemoteAnimationProvider mRemoteAnimationProvider;
    131 
    132     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
    133         @Override
    134         public void onAnimationStart(Animator animation) {
    135             mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
    136         }
    137 
    138         @Override
    139         public void onAnimationEnd(Animator animation) {
    140             mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
    141         }
    142     };
    143 
    144     public LauncherAppTransitionManagerImpl(Context context) {
    145         mLauncher = Launcher.getLauncher(context);
    146         mDragLayer = mLauncher.getDragLayer();
    147         mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
    148         mHandler = new Handler(Looper.getMainLooper());
    149         mIsRtl = Utilities.isRtl(mLauncher.getResources());
    150         mDeviceProfile = mLauncher.getDeviceProfile();
    151 
    152         Resources res = mLauncher.getResources();
    153         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
    154         mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
    155         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
    156 
    157         mLauncher.addOnDeviceProfileChangeListener(this);
    158         registerRemoteAnimations();
    159     }
    160 
    161     @Override
    162     public void onDeviceProfileChanged(DeviceProfile dp) {
    163         mDeviceProfile = dp;
    164     }
    165 
    166     /**
    167      * @return ActivityOptions with remote animations that controls how the window of the opening
    168      *         targets are displayed.
    169      */
    170     @Override
    171     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
    172         if (hasControlRemoteAppTransitionPermission()) {
    173             RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
    174                     true /* startAtFrontOfQueue */) {
    175 
    176                 @Override
    177                 public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
    178                         AnimationResult result) {
    179                     AnimatorSet anim = new AnimatorSet();
    180 
    181                     boolean launcherClosing =
    182                             launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
    183 
    184                     if (!composeRecentsLaunchAnimator(v, targetCompats, anim)) {
    185                         // Set the state animation first so that any state listeners are called
    186                         // before our internal listeners.
    187                         mLauncher.getStateManager().setCurrentAnimation(anim);
    188 
    189                         Rect windowTargetBounds = getWindowTargetBounds(targetCompats);
    190                         anim.play(getIconAnimator(v, windowTargetBounds));
    191                         if (launcherClosing) {
    192                             Pair<AnimatorSet, Runnable> launcherContentAnimator =
    193                                     getLauncherContentAnimator(true /* isAppOpening */);
    194                             anim.play(launcherContentAnimator.first);
    195                             anim.addListener(new AnimatorListenerAdapter() {
    196                                 @Override
    197                                 public void onAnimationEnd(Animator animation) {
    198                                     launcherContentAnimator.second.run();
    199                                 }
    200                             });
    201                         }
    202                         anim.play(getOpeningWindowAnimators(v, targetCompats, windowTargetBounds));
    203                     }
    204 
    205                     if (launcherClosing) {
    206                         anim.addListener(mForceInvisibleListener);
    207                     }
    208 
    209                     result.setAnimation(anim);
    210                 }
    211             };
    212 
    213             int duration = findTaskViewToLaunch(launcher, v, null) != null
    214                     ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
    215             int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
    216             return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
    217                     runner, duration, statusBarTransitionDelay));
    218         }
    219         return super.getActivityLaunchOptions(launcher, v);
    220     }
    221 
    222     /**
    223      * Return the window bounds of the opening target.
    224      * In multiwindow mode, we need to get the final size of the opening app window target to help
    225      * figure out where the floating view should animate to.
    226      */
    227     private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] targets) {
    228         Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
    229         if (mLauncher.isInMultiWindowModeCompat()) {
    230             for (RemoteAnimationTargetCompat target : targets) {
    231                 if (target.mode == MODE_OPENING) {
    232                     bounds.set(target.sourceContainerBounds);
    233                     bounds.offsetTo(target.position.x, target.position.y);
    234                     return bounds;
    235                 }
    236             }
    237         }
    238         return bounds;
    239     }
    240 
    241     public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
    242             CancellationSignal cancellationSignal) {
    243         mRemoteAnimationProvider = animationProvider;
    244         cancellationSignal.setOnCancelListener(() -> {
    245             if (animationProvider == mRemoteAnimationProvider) {
    246                 mRemoteAnimationProvider = null;
    247             }
    248         });
    249     }
    250 
    251     /**
    252      * Composes the animations for a launch from the recents list if possible.
    253      */
    254     private boolean composeRecentsLaunchAnimator(View v,
    255             RemoteAnimationTargetCompat[] targets, AnimatorSet target) {
    256         // Ensure recents is actually visible
    257         if (!mLauncher.getStateManager().getState().overviewUi) {
    258             return false;
    259         }
    260 
    261         RecentsView recentsView = mLauncher.getOverviewPanel();
    262         boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
    263         boolean skipLauncherChanges = !launcherClosing;
    264         boolean isLaunchingFromQuickscrub =
    265                 recentsView.getQuickScrubController().isWaitingForTaskLaunch();
    266 
    267         TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
    268         if (taskView == null) {
    269             return false;
    270         }
    271 
    272         int duration = isLaunchingFromQuickscrub
    273                 ? RECENTS_QUICKSCRUB_LAUNCH_DURATION
    274                 : RECENTS_LAUNCH_DURATION;
    275 
    276         ClipAnimationHelper helper = new ClipAnimationHelper();
    277         target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
    278                 .setDuration(duration));
    279 
    280         Animator childStateAnimation = null;
    281         // Found a visible recents task that matches the opening app, lets launch the app from there
    282         Animator launcherAnim;
    283         final AnimatorListenerAdapter windowAnimEndListener;
    284         if (launcherClosing) {
    285             launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
    286             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
    287             launcherAnim.setDuration(duration);
    288 
    289             // Make sure recents gets fixed up by resetting task alphas and scales, etc.
    290             windowAnimEndListener = new AnimatorListenerAdapter() {
    291                 @Override
    292                 public void onAnimationEnd(Animator animation) {
    293                     mLauncher.getStateManager().moveToRestState();
    294                     mLauncher.getStateManager().reapplyState();
    295                 }
    296             };
    297         } else {
    298             AnimatorPlaybackController controller =
    299                     mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL, duration);
    300             controller.dispatchOnStart();
    301             childStateAnimation = controller.getTarget();
    302             launcherAnim = controller.getAnimationPlayer().setDuration(duration);
    303             windowAnimEndListener = new AnimatorListenerAdapter() {
    304                 @Override
    305                 public void onAnimationEnd(Animator animation) {
    306                     mLauncher.getStateManager().goToState(NORMAL, false);
    307                 }
    308             };
    309         }
    310         target.play(launcherAnim);
    311 
    312         // Set the current animation first, before adding windowAnimEndListener. Setting current
    313         // animation adds some listeners which need to be called before windowAnimEndListener
    314         // (the ordering of listeners matter in this case).
    315         mLauncher.getStateManager().setCurrentAnimation(target, childStateAnimation);
    316         target.addListener(windowAnimEndListener);
    317         return true;
    318     }
    319 
    320     /**
    321      * Content is everything on screen except the background and the floating view (if any).
    322      *
    323      * @param isAppOpening True when this is called when an app is opening.
    324      *                     False when this is called when an app is closing.
    325      */
    326     private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
    327         AnimatorSet launcherAnimator = new AnimatorSet();
    328         Runnable endListener;
    329 
    330         float[] alphas = isAppOpening
    331                 ? new float[] {1, 0}
    332                 : new float[] {0, 1};
    333         float[] trans = isAppOpening
    334                 ? new float[] {0, mContentTransY}
    335                 : new float[] {-mContentTransY, 0};
    336 
    337         if (mLauncher.isInState(ALL_APPS)) {
    338             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
    339             final View appsView = mLauncher.getAppsView();
    340             final float startAlpha = appsView.getAlpha();
    341             final float startY = appsView.getTranslationY();
    342             appsView.setAlpha(alphas[0]);
    343             appsView.setTranslationY(trans[0]);
    344 
    345             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
    346             alpha.setDuration(217);
    347             alpha.setInterpolator(LINEAR);
    348             appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    349             alpha.addListener(new AnimatorListenerAdapter() {
    350                 @Override
    351                 public void onAnimationEnd(Animator animation) {
    352                     appsView.setLayerType(View.LAYER_TYPE_NONE, null);
    353                 }
    354             });
    355             ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
    356             transY.setInterpolator(AGGRESSIVE_EASE);
    357             transY.setDuration(350);
    358 
    359             launcherAnimator.play(alpha);
    360             launcherAnimator.play(transY);
    361 
    362             endListener = () -> {
    363                 appsView.setAlpha(startAlpha);
    364                 appsView.setTranslationY(startY);
    365                 appsView.setLayerType(View.LAYER_TYPE_NONE, null);
    366             };
    367         } else if (mLauncher.isInState(OVERVIEW)) {
    368             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
    369             launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
    370                     allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
    371 
    372             View overview = mLauncher.getOverviewPanelContainer();
    373             ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, View.ALPHA, alphas);
    374             alpha.setDuration(217);
    375             alpha.setInterpolator(LINEAR);
    376             launcherAnimator.play(alpha);
    377 
    378             ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
    379             transY.setInterpolator(AGGRESSIVE_EASE);
    380             transY.setDuration(350);
    381             launcherAnimator.play(transY);
    382 
    383             overview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    384 
    385             endListener = () -> {
    386                 overview.setLayerType(View.LAYER_TYPE_NONE, null);
    387                 overview.setAlpha(1f);
    388                 overview.setTranslationY(0f);
    389                 mLauncher.getStateManager().reapplyState();
    390             };
    391         } else {
    392             mDragLayerAlpha.setValue(alphas[0]);
    393             ObjectAnimator alpha =
    394                     ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
    395             alpha.setDuration(217);
    396             alpha.setInterpolator(LINEAR);
    397             launcherAnimator.play(alpha);
    398 
    399             mDragLayer.setTranslationY(trans[0]);
    400             ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
    401             transY.setInterpolator(AGGRESSIVE_EASE);
    402             transY.setDuration(350);
    403             launcherAnimator.play(transY);
    404 
    405             mDragLayer.getScrim().hideSysUiScrim(true);
    406             // Pause page indicator animations as they lead to layer trashing.
    407             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
    408             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    409 
    410             endListener = this::resetContentView;
    411         }
    412         return new Pair<>(launcherAnimator, endListener);
    413     }
    414 
    415     /**
    416      * @return Animator that controls the icon used to launch the target.
    417      */
    418     private AnimatorSet getIconAnimator(View v, Rect windowTargetBounds) {
    419         final boolean isBubbleTextView = v instanceof BubbleTextView;
    420         mFloatingView = new View(mLauncher);
    421         if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
    422             // Create a copy of the app icon
    423             mFloatingView.setBackground(
    424                     DrawableFactory.get(mLauncher).newIcon((ItemInfoWithIcon) v.getTag()));
    425         }
    426 
    427         // Position the floating view exactly on top of the original
    428         Rect rect = new Rect();
    429         final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
    430         if (fromDeepShortcutView) {
    431             // Deep shortcut views have their icon drawn in a separate view.
    432             DeepShortcutView view = (DeepShortcutView) v.getParent();
    433             mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
    434         } else {
    435             mDragLayer.getDescendantRectRelativeToSelf(v, rect);
    436         }
    437         int viewLocationLeft = rect.left;
    438         int viewLocationTop = rect.top;
    439 
    440         float startScale = 1f;
    441         if (isBubbleTextView && !fromDeepShortcutView) {
    442             BubbleTextView btv = (BubbleTextView) v;
    443             btv.getIconBounds(rect);
    444             Drawable dr = btv.getIcon();
    445             if (dr instanceof FastBitmapDrawable) {
    446                 startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
    447             }
    448         } else {
    449             rect.set(0, 0, rect.width(), rect.height());
    450         }
    451         viewLocationLeft += rect.left;
    452         viewLocationTop += rect.top;
    453         int viewLocationStart = mIsRtl
    454                 ? windowTargetBounds.width() - rect.right
    455                 : viewLocationLeft;
    456         LayoutParams lp = new LayoutParams(rect.width(), rect.height());
    457         lp.ignoreInsets = true;
    458         lp.setMarginStart(viewLocationStart);
    459         lp.topMargin = viewLocationTop;
    460         mFloatingView.setLayoutParams(lp);
    461 
    462         // Set the properties here already to make sure they'are available when running the first
    463         // animation frame.
    464         mFloatingView.setLeft(viewLocationLeft);
    465         mFloatingView.setTop(viewLocationTop);
    466         mFloatingView.setRight(viewLocationLeft + rect.width());
    467         mFloatingView.setBottom(viewLocationTop + rect.height());
    468 
    469         // Swap the two views in place.
    470         ((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
    471         v.setVisibility(View.INVISIBLE);
    472 
    473         AnimatorSet appIconAnimatorSet = new AnimatorSet();
    474         int[] dragLayerBounds = new int[2];
    475         mDragLayer.getLocationOnScreen(dragLayerBounds);
    476 
    477         // Animate the app icon to the center of the window bounds in screen coordinates.
    478         float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
    479         float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
    480 
    481         float xPosition = mIsRtl
    482                 ? windowTargetBounds.width() - lp.getMarginStart() - rect.width()
    483                 : lp.getMarginStart();
    484         float dX = centerX - xPosition - (lp.width / 2);
    485         float dY = centerY - lp.topMargin - (lp.height / 2);
    486 
    487         ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX);
    488         ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY);
    489 
    490         // Use upward animation for apps that are either on the bottom half of the screen, or are
    491         // relatively close to the center.
    492         boolean useUpwardAnimation = lp.topMargin > centerY
    493                 || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
    494         if (useUpwardAnimation) {
    495             x.setDuration(APP_LAUNCH_CURVED_DURATION);
    496             y.setDuration(APP_LAUNCH_DURATION);
    497         } else {
    498             x.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_DURATION));
    499             y.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_CURVED_DURATION));
    500         }
    501         x.setInterpolator(AGGRESSIVE_EASE);
    502         y.setInterpolator(AGGRESSIVE_EASE);
    503         appIconAnimatorSet.play(x);
    504         appIconAnimatorSet.play(y);
    505 
    506         // Scale the app icon to take up the entire screen. This simplifies the math when
    507         // animating the app window position / scale.
    508         float maxScaleX = windowTargetBounds.width() / (float) rect.width();
    509         float maxScaleY = windowTargetBounds.height() / (float) rect.height();
    510         float scale = Math.max(maxScaleX, maxScaleY);
    511         ObjectAnimator scaleAnim = ObjectAnimator
    512                 .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
    513         scaleAnim.setDuration(APP_LAUNCH_DURATION)
    514                 .setInterpolator(Interpolators.EXAGGERATED_EASE);
    515         appIconAnimatorSet.play(scaleAnim);
    516 
    517         // Fade out the app icon.
    518         ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
    519         if (useUpwardAnimation) {
    520             alpha.setStartDelay(APP_LAUNCH_ALPHA_START_DELAY);
    521             alpha.setDuration(APP_LAUNCH_ALPHA_DURATION);
    522         } else {
    523             alpha.setStartDelay((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR
    524                     * APP_LAUNCH_ALPHA_START_DELAY));
    525             alpha.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_ALPHA_DURATION));
    526         }
    527         alpha.setInterpolator(LINEAR);
    528         appIconAnimatorSet.play(alpha);
    529 
    530         appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
    531             @Override
    532             public void onAnimationEnd(Animator animation) {
    533                 // Reset launcher to normal state
    534                 v.setVisibility(View.VISIBLE);
    535                 ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
    536             }
    537         });
    538         return appIconAnimatorSet;
    539     }
    540 
    541     /**
    542      * @return Animator that controls the window of the opening targets.
    543      */
    544     private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
    545             Rect windowTargetBounds) {
    546         Rect bounds = new Rect();
    547         if (v.getParent() instanceof DeepShortcutView) {
    548             // Deep shortcut views have their icon drawn in a separate view.
    549             DeepShortcutView view = (DeepShortcutView) v.getParent();
    550             mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
    551         } else if (v instanceof BubbleTextView) {
    552             ((BubbleTextView) v).getIconBounds(bounds);
    553         } else {
    554             mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
    555         }
    556         int[] floatingViewBounds = new int[2];
    557 
    558         Rect crop = new Rect();
    559         Matrix matrix = new Matrix();
    560 
    561         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
    562         appAnimator.setDuration(APP_LAUNCH_DURATION);
    563         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
    564             // Fade alpha for the app window.
    565             FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR);
    566             boolean isFirstFrame = true;
    567 
    568             @Override
    569             public void onUpdate(float percent) {
    570                 final Surface surface = getSurface(mFloatingView);
    571                 final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
    572                 if (frameNumber == -1) {
    573                     // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
    574                     Log.w(TAG, "Failed to animate, surface got destroyed.");
    575                     return;
    576                 }
    577                 final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent);
    578 
    579                 // Calculate app icon size.
    580                 float iconWidth = bounds.width() * mFloatingView.getScaleX();
    581                 float iconHeight = bounds.height() * mFloatingView.getScaleY();
    582 
    583                 // Scale the app window to match the icon size.
    584                 float scaleX = iconWidth / windowTargetBounds.width();
    585                 float scaleY = iconHeight / windowTargetBounds.height();
    586                 float scale = Math.min(1f, Math.min(scaleX, scaleY));
    587                 matrix.setScale(scale, scale);
    588 
    589                 // Position the scaled window on top of the icon
    590                 int windowWidth = windowTargetBounds.width();
    591                 int windowHeight = windowTargetBounds.height();
    592                 float scaledWindowWidth = windowWidth * scale;
    593                 float scaledWindowHeight = windowHeight * scale;
    594 
    595                 float offsetX = (scaledWindowWidth - iconWidth) / 2;
    596                 float offsetY = (scaledWindowHeight - iconHeight) / 2;
    597                 mFloatingView.getLocationOnScreen(floatingViewBounds);
    598 
    599                 float transX0 = floatingViewBounds[0] - offsetX;
    600                 float transY0 = floatingViewBounds[1] - offsetY;
    601                 matrix.postTranslate(transX0, transY0);
    602 
    603                 // Animate the window crop so that it starts off as a square, and then reveals
    604                 // horizontally.
    605                 float cropHeight = windowHeight * easePercent + windowWidth * (1 - easePercent);
    606                 float initialTop = (windowHeight - windowWidth) / 2f;
    607                 crop.left = 0;
    608                 crop.top = (int) (initialTop * (1 - easePercent));
    609                 crop.right = windowWidth;
    610                 crop.bottom = (int) (crop.top + cropHeight);
    611 
    612                 TransactionCompat t = new TransactionCompat();
    613                 if (isFirstFrame) {
    614                     RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_OPENING);
    615                     isFirstFrame = false;
    616                 }
    617                 for (RemoteAnimationTargetCompat target : targets) {
    618                     if (target.mode == MODE_OPENING) {
    619                         t.setAlpha(target.leash, mAlpha.value);
    620                         t.setMatrix(target.leash, matrix);
    621                         t.setWindowCrop(target.leash, crop);
    622                         t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
    623                     }
    624                 }
    625                 t.setEarlyWakeup();
    626                 t.apply();
    627 
    628                 matrix.reset();
    629             }
    630         });
    631         return appAnimator;
    632     }
    633 
    634     /**
    635      * Registers remote animations used when closing apps to home screen.
    636      */
    637     private void registerRemoteAnimations() {
    638         // Unregister this
    639         if (hasControlRemoteAppTransitionPermission()) {
    640             RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
    641             definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
    642                     WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
    643                     new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
    644                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
    645 
    646             // TODO: Transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
    647             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
    648         }
    649     }
    650 
    651     private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
    652         return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
    653     }
    654 
    655     /**
    656      * @return Runner that plays when user goes to Launcher
    657      *         ie. pressing home, swiping up from nav bar.
    658      */
    659     private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
    660         return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
    661             @Override
    662             public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
    663                     AnimationResult result) {
    664                 if (!mLauncher.hasBeenResumed()) {
    665                     // If launcher is not resumed, wait until new async-frame after resume
    666                     mLauncher.setOnResumeCallback(() ->
    667                             postAsyncCallback(mHandler, () ->
    668                                     onCreateAnimation(targetCompats, result)));
    669                     return;
    670                 }
    671 
    672                 AnimatorSet anim = null;
    673                 RemoteAnimationProvider provider = mRemoteAnimationProvider;
    674                 if (provider != null) {
    675                     anim = provider.createWindowAnimation(targetCompats);
    676                 }
    677 
    678                 if (anim == null) {
    679                     anim = new AnimatorSet();
    680                     anim.play(getClosingWindowAnimators(targetCompats));
    681 
    682                     // Normally, we run the launcher content animation when we are transitioning
    683                     // home, but if home is already visible, then we don't want to animate the
    684                     // contents of launcher unless we know that we are animating home as a result
    685                     // of the home button press with quickstep, which will result in launcher being
    686                     // started on touch down, prior to the animation home (and won't be in the
    687                     // targets list because it is already visible). In that case, we force
    688                     // invisibility on touch down, and only reset it after the animation to home
    689                     // is initialized.
    690                     if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
    691                             || mLauncher.isForceInvisible()) {
    692                         // Only register the content animation for cancellation when state changes
    693                         mLauncher.getStateManager().setCurrentAnimation(anim);
    694                         createLauncherResumeAnimation(anim);
    695                     }
    696                 }
    697 
    698                 mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
    699                 result.setAnimation(anim);
    700             }
    701         };
    702     }
    703 
    704     /**
    705      * Animator that controls the transformations of the windows the targets that are closing.
    706      */
    707     private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
    708         Matrix matrix = new Matrix();
    709         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
    710         int duration = CLOSING_TRANSITION_DURATION_MS;
    711         closingAnimator.setDuration(duration);
    712         closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
    713             FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
    714             FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
    715             FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
    716 
    717             boolean isFirstFrame = true;
    718 
    719             @Override
    720             public void onUpdate(float percent) {
    721                 TransactionCompat t = new TransactionCompat();
    722                 if (isFirstFrame) {
    723                     RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_CLOSING);
    724                     isFirstFrame = false;
    725                 }
    726                 for (RemoteAnimationTargetCompat app : targets) {
    727                     if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
    728                         t.setAlpha(app.leash, mAlpha.value);
    729                         matrix.setScale(mScale.value, mScale.value,
    730                                 app.sourceContainerBounds.centerX(),
    731                                 app.sourceContainerBounds.centerY());
    732                         matrix.postTranslate(0, mDy.value);
    733                         matrix.postTranslate(app.position.x, app.position.y);
    734                         t.setMatrix(app.leash, matrix);
    735                     }
    736                 }
    737                 t.setEarlyWakeup();
    738                 t.apply();
    739 
    740                 matrix.reset();
    741             }
    742         });
    743 
    744         return closingAnimator;
    745     }
    746 
    747     /**
    748      * Creates an animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
    749      */
    750     private void createLauncherResumeAnimation(AnimatorSet anim) {
    751         if (mLauncher.isInState(LauncherState.ALL_APPS)) {
    752             Pair<AnimatorSet, Runnable> contentAnimator =
    753                     getLauncherContentAnimator(false /* isAppOpening */);
    754             contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
    755             anim.play(contentAnimator.first);
    756             anim.addListener(new AnimatorListenerAdapter() {
    757                 @Override
    758                 public void onAnimationEnd(Animator animation) {
    759                     contentAnimator.second.run();
    760                 }
    761             });
    762         } else {
    763             AnimatorSet workspaceAnimator = new AnimatorSet();
    764 
    765             mDragLayer.setTranslationY(-mWorkspaceTransY);;
    766             workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
    767                     -mWorkspaceTransY, 0));
    768 
    769             mDragLayerAlpha.setValue(0);
    770             workspaceAnimator.play(ObjectAnimator.ofFloat(
    771                     mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
    772 
    773             workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
    774             workspaceAnimator.setDuration(333);
    775             workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
    776 
    777             mDragLayer.getScrim().hideSysUiScrim(true);
    778 
    779             // Pause page indicator animations as they lead to layer trashing.
    780             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
    781             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    782 
    783             workspaceAnimator.addListener(new AnimatorListenerAdapter() {
    784                 @Override
    785                 public void onAnimationEnd(Animator animation) {
    786                     resetContentView();
    787                 }
    788             });
    789             anim.play(workspaceAnimator);
    790         }
    791     }
    792 
    793     private void resetContentView() {
    794         mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
    795         mDragLayerAlpha.setValue(1f);
    796         mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
    797         mDragLayer.setTranslationY(0f);
    798         mDragLayer.getScrim().hideSysUiScrim(false);
    799     }
    800 
    801     private boolean hasControlRemoteAppTransitionPermission() {
    802         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
    803                 == PackageManager.PERMISSION_GRANTED;
    804     }
    805 }
    806