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 android.util.TypedValue.COMPLEX_UNIT_DIP;
20 
21 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
22 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
23 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
24 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
25 import static com.android.launcher3.LauncherState.ALL_APPS;
26 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
27 import static com.android.launcher3.LauncherState.OVERVIEW;
28 import static com.android.launcher3.Utilities.postAsyncCallback;
29 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
30 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
31 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
32 import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
33 import static com.android.launcher3.anim.Interpolators.LINEAR;
34 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
35 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
36 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
37 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
38 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
39 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
40 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
41 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
42 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
43 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
44 
45 import android.animation.Animator;
46 import android.animation.AnimatorListenerAdapter;
47 import android.animation.AnimatorSet;
48 import android.animation.ObjectAnimator;
49 import android.animation.ValueAnimator;
50 import android.annotation.TargetApi;
51 import android.app.ActivityOptions;
52 import android.content.Context;
53 import android.content.pm.PackageManager;
54 import android.content.res.Resources;
55 import android.graphics.Matrix;
56 import android.graphics.Point;
57 import android.graphics.Rect;
58 import android.graphics.RectF;
59 import android.graphics.drawable.Drawable;
60 import android.os.Build;
61 import android.os.CancellationSignal;
62 import android.os.Handler;
63 import android.os.Looper;
64 import android.util.Pair;
65 import android.util.TypedValue;
66 import android.view.View;
67 
68 import androidx.annotation.NonNull;
69 import androidx.annotation.Nullable;
70 
71 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
72 import com.android.launcher3.allapps.AllAppsTransitionController;
73 import com.android.launcher3.config.FeatureFlags;
74 import com.android.launcher3.dragndrop.DragLayer;
75 import com.android.launcher3.shortcuts.DeepShortcutView;
76 import com.android.launcher3.statehandlers.DepthController;
77 import com.android.launcher3.util.DynamicResource;
78 import com.android.launcher3.util.MultiValueAlpha;
79 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
80 import com.android.launcher3.views.FloatingIconView;
81 import com.android.quickstep.RemoteAnimationTargets;
82 import com.android.quickstep.util.MultiValueUpdateListener;
83 import com.android.quickstep.util.RemoteAnimationProvider;
84 import com.android.quickstep.util.StaggeredWorkspaceAnim;
85 import com.android.quickstep.util.SurfaceTransactionApplier;
86 import com.android.systemui.shared.system.ActivityCompat;
87 import com.android.systemui.shared.system.ActivityOptionsCompat;
88 import com.android.systemui.shared.system.QuickStepContract;
89 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
90 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
91 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
92 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
93 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
94 import com.android.systemui.shared.system.WindowManagerWrapper;
95 
96 /**
97  * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
98  * home and/or all-apps.  Not used for 3p launchers.
99  */
100 @TargetApi(Build.VERSION_CODES.O)
101 @SuppressWarnings("unused")
102 public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
103         implements OnDeviceProfileChangeListener {
104 
105     private static final String TAG = "QuickstepTransition";
106 
107     /** Duration of status bar animations. */
108     public static final int STATUS_BAR_TRANSITION_DURATION = 120;
109 
110     /**
111      * Since our animations decelerate heavily when finishing, we want to start status bar animations
112      * x ms before the ending.
113      */
114     public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
115 
116     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
117             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
118 
119     private static final long APP_LAUNCH_DURATION = 450;
120     // Use a shorter duration for x or y translation to create a curve effect
121     private static final long APP_LAUNCH_CURVED_DURATION = 250;
122     private static final long APP_LAUNCH_ALPHA_DURATION = 50;
123     private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
124 
125     // We scale the durations for the downward app launch animations (minus the scale animation).
126     private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
127     private static final long APP_LAUNCH_DOWN_DURATION =
128             (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
129     private static final long APP_LAUNCH_DOWN_CURVED_DURATION =
130             (long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
131     private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
132             (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
133 
134     private static final long CROP_DURATION = 375;
135     private static final long RADIUS_DURATION = 375;
136 
137     public static final int RECENTS_LAUNCH_DURATION = 336;
138     private static final int LAUNCHER_RESUME_START_DELAY = 100;
139     private static final int CLOSING_TRANSITION_DURATION_MS = 250;
140 
141     protected static final int CONTENT_ALPHA_DURATION = 217;
142     protected static final int CONTENT_TRANSLATION_DURATION = 350;
143 
144     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
145     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
146 
147     protected final BaseQuickstepLauncher mLauncher;
148 
149     private final DragLayer mDragLayer;
150     private final AlphaProperty mDragLayerAlpha;
151 
152     final Handler mHandler;
153     private final boolean mIsRtl;
154 
155     private final float mContentTransY;
156     private final float mWorkspaceTransY;
157     private final float mClosingWindowTransY;
158 
159     private DeviceProfile mDeviceProfile;
160 
161     private RemoteAnimationProvider mRemoteAnimationProvider;
162     // Strong refs to runners which are cleared when the launcher activity is destroyed
163     private WrappedAnimationRunnerImpl mWallpaperOpenRunner;
164     private WrappedAnimationRunnerImpl mAppLaunchRunner;
165     private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner;
166 
167     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
168         @Override
169         public void onAnimationStart(Animator animation) {
170             mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
171         }
172 
173         @Override
174         public void onAnimationEnd(Animator animation) {
175             mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
176         }
177     };
178 
QuickstepAppTransitionManagerImpl(Context context)179     public QuickstepAppTransitionManagerImpl(Context context) {
180         mLauncher = Launcher.cast(Launcher.getLauncher(context));
181         mDragLayer = mLauncher.getDragLayer();
182         mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
183         mHandler = new Handler(Looper.getMainLooper());
184         mIsRtl = Utilities.isRtl(mLauncher.getResources());
185         mDeviceProfile = mLauncher.getDeviceProfile();
186 
187         Resources res = mLauncher.getResources();
188         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
189         mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
190         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
191 
192         mLauncher.addOnDeviceProfileChangeListener(this);
193     }
194 
195     @Override
onDeviceProfileChanged(DeviceProfile dp)196     public void onDeviceProfileChanged(DeviceProfile dp) {
197         mDeviceProfile = dp;
198     }
199 
200     @Override
supportsAdaptiveIconAnimation()201     public boolean supportsAdaptiveIconAnimation() {
202         return hasControlRemoteAppTransitionPermission()
203                 && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get();
204     }
205 
206     /**
207      * @return ActivityOptions with remote animations that controls how the window of the opening
208      *         targets are displayed.
209      */
210     @Override
getActivityLaunchOptions(Launcher launcher, View v)211     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
212         if (hasControlRemoteAppTransitionPermission()) {
213             boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
214             mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v);
215             RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
216                     mAppLaunchRunner, true /* startAtFrontOfQueue */);
217 
218             // Note that this duration is a guess as we do not know if the animation will be a
219             // recents launch or not for sure until we know the opening app targets.
220             long duration = fromRecents
221                     ? RECENTS_LAUNCH_DURATION
222                     : APP_LAUNCH_DURATION;
223 
224             long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
225                     - STATUS_BAR_TRANSITION_PRE_DELAY;
226             return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
227                     runner, duration, statusBarTransitionDelay));
228         }
229         return super.getActivityLaunchOptions(launcher, v);
230     }
231 
232     /**
233      * Whether the launch is a recents app transition and we should do a launch animation
234      * from the recents view. Note that if the remote animation targets are not provided, this
235      * may not always be correct as we may resolve the opening app to a task when the animation
236      * starts.
237      *
238      * @param v the view to launch from
239      * @param targets apps that are opening/closing
240      * @return true if the app is launching from recents, false if it most likely is not
241      */
isLaunchingFromRecents(@onNull View v, @Nullable RemoteAnimationTargetCompat[] targets)242     protected abstract boolean isLaunchingFromRecents(@NonNull View v,
243             @Nullable RemoteAnimationTargetCompat[] targets);
244 
245     /**
246      * Composes the animations for a launch from the recents list.
247      *
248      * @param anim the animator set to add to
249      * @param v the launching view
250      * @param appTargets the apps that are opening/closing
251      * @param launcherClosing true if the launcher app is closing
252      */
composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing)253     protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
254             @NonNull RemoteAnimationTargetCompat[] appTargets,
255             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
256 
257     /**
258      * Compose the animations for a launch from the app icon.
259      *
260      * @param anim the animation to add to
261      * @param v the launching view with the icon
262      * @param appTargets the list of opening/closing apps
263      * @param launcherClosing true if launcher is closing
264      */
composeIconLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing)265     private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
266             @NonNull RemoteAnimationTargetCompat[] appTargets,
267             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
268             boolean launcherClosing) {
269         // Set the state animation first so that any state listeners are called
270         // before our internal listeners.
271         mLauncher.getStateManager().setCurrentAnimation(anim);
272 
273         Rect windowTargetBounds = getWindowTargetBounds(appTargets);
274         boolean isAllOpeningTargetTrs = true;
275         for (int i = 0; i < appTargets.length; i++) {
276             RemoteAnimationTargetCompat target = appTargets[i];
277             if (target.mode == MODE_OPENING) {
278                 isAllOpeningTargetTrs &= target.isTranslucent;
279             }
280             if (!isAllOpeningTargetTrs) break;
281         }
282         anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
283                 !isAllOpeningTargetTrs));
284         if (launcherClosing) {
285             Pair<AnimatorSet, Runnable> launcherContentAnimator =
286                     getLauncherContentAnimator(true /* isAppOpening */,
287                             new float[] {0, -mContentTransY});
288             anim.play(launcherContentAnimator.first);
289             anim.addListener(new AnimatorListenerAdapter() {
290                 @Override
291                 public void onAnimationEnd(Animator animation) {
292                     launcherContentAnimator.second.run();
293                 }
294             });
295         } else {
296             anim.addListener(new AnimatorListenerAdapter() {
297                 @Override
298                 public void onAnimationStart(Animator animation) {
299                     mLauncher.addOnResumeCallback(() ->
300                             ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
301                             mLauncher.getStateManager().getState().getDepth(mLauncher)).start());
302                 }
303             });
304         }
305     }
306 
307     /**
308      * Return the window bounds of the opening target.
309      * In multiwindow mode, we need to get the final size of the opening app window target to help
310      * figure out where the floating view should animate to.
311      */
getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets)312     private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets) {
313         Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
314         if (mLauncher.isInMultiWindowMode()) {
315             for (RemoteAnimationTargetCompat target : appTargets) {
316                 if (target.mode == MODE_OPENING) {
317                     bounds.set(target.screenSpaceBounds);
318                     if (target.localBounds != null) {
319                         bounds.set(target.localBounds);
320                     } else {
321                         bounds.offsetTo(target.position.x, target.position.y);
322                     }
323                     return bounds;
324                 }
325             }
326         }
327         return bounds;
328     }
329 
setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, CancellationSignal cancellationSignal)330     public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
331             CancellationSignal cancellationSignal) {
332         mRemoteAnimationProvider = animationProvider;
333         cancellationSignal.setOnCancelListener(() -> {
334             if (animationProvider == mRemoteAnimationProvider) {
335                 mRemoteAnimationProvider = null;
336             }
337         });
338     }
339 
340     /**
341      * Content is everything on screen except the background and the floating view (if any).
342      *
343      * @param isAppOpening True when this is called when an app is opening.
344      *                     False when this is called when an app is closing.
345      * @param trans Array that contains the start and end translation values for the content.
346      */
getLauncherContentAnimator(boolean isAppOpening, float[] trans)347     private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
348             float[] trans) {
349         AnimatorSet launcherAnimator = new AnimatorSet();
350         Runnable endListener;
351 
352         float[] alphas = isAppOpening
353                 ? new float[] {1, 0}
354                 : new float[] {0, 1};
355 
356         if (mLauncher.isInState(ALL_APPS)) {
357             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
358             final View appsView = mLauncher.getAppsView();
359             final float startAlpha = appsView.getAlpha();
360             final float startY = appsView.getTranslationY();
361             appsView.setAlpha(alphas[0]);
362             appsView.setTranslationY(trans[0]);
363 
364             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
365             alpha.setDuration(CONTENT_ALPHA_DURATION);
366             alpha.setInterpolator(LINEAR);
367             appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
368             alpha.addListener(new AnimatorListenerAdapter() {
369                 @Override
370                 public void onAnimationEnd(Animator animation) {
371                     appsView.setLayerType(View.LAYER_TYPE_NONE, null);
372                 }
373             });
374             ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
375             transY.setInterpolator(AGGRESSIVE_EASE);
376             transY.setDuration(CONTENT_TRANSLATION_DURATION);
377 
378             launcherAnimator.play(alpha);
379             launcherAnimator.play(transY);
380 
381             endListener = () -> {
382                 appsView.setAlpha(startAlpha);
383                 appsView.setTranslationY(startY);
384                 appsView.setLayerType(View.LAYER_TYPE_NONE, null);
385             };
386         } else if (mLauncher.isInState(OVERVIEW)) {
387             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
388             launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
389                     allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
390             endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
391         } else {
392             mDragLayerAlpha.setValue(alphas[0]);
393             ObjectAnimator alpha =
394                     ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
395             alpha.setDuration(CONTENT_ALPHA_DURATION);
396             alpha.setInterpolator(LINEAR);
397             launcherAnimator.play(alpha);
398 
399             Workspace workspace = mLauncher.getWorkspace();
400             View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage()))
401                     .getShortcutsAndWidgets();
402             View hotseat = mLauncher.getHotseat();
403             View qsb = mLauncher.findViewById(R.id.search_container_all_apps);
404 
405             currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
406             hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
407             qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null);
408 
409             launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans));
410             launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans));
411             launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans));
412 
413             // Pause page indicator animations as they lead to layer trashing.
414             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
415 
416             endListener = () -> {
417                 currentPage.setTranslationY(0);
418                 hotseat.setTranslationY(0);
419                 qsb.setTranslationY(0);
420 
421                 currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
422                 hotseat.setLayerType(View.LAYER_TYPE_NONE, null);
423                 qsb.setLayerType(View.LAYER_TYPE_NONE, null);
424 
425                 mDragLayerAlpha.setValue(1f);
426                 mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
427             };
428         }
429         return new Pair<>(launcherAnimator, endListener);
430     }
431 
432     /**
433      * Compose recents view alpha and translation Y animation when launcher opens/closes apps.
434      *
435      * @param anim the animator set to add to
436      * @param alphas the alphas to animate to over time
437      * @param trans the translation Y values to animator to over time
438      * @return listener to run when the animation ends
439      */
composeViewContentAnimator(@onNull AnimatorSet anim, float[] alphas, float[] trans)440     protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
441             float[] alphas, float[] trans);
442 
443     /**
444      * @return Animator that controls the window of the opening targets from app icons.
445      */
getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, Rect windowTargetBounds, boolean toggleVisibility)446     private Animator getOpeningWindowAnimators(View v,
447             RemoteAnimationTargetCompat[] appTargets,
448             RemoteAnimationTargetCompat[] wallpaperTargets,
449             Rect windowTargetBounds, boolean toggleVisibility) {
450         RectF bounds = new RectF();
451         FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
452                 toggleVisibility, bounds, true /* isOpening */);
453         Rect crop = new Rect();
454         Matrix matrix = new Matrix();
455 
456         RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
457                 wallpaperTargets, MODE_OPENING);
458         SurfaceTransactionApplier surfaceApplier =
459                 new SurfaceTransactionApplier(floatingView);
460         openingTargets.addReleaseCheck(surfaceApplier);
461 
462         // Scale the app icon to take up the entire screen. This simplifies the math when
463         // animating the app window position / scale.
464         float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
465         float maxScaleX = smallestSize / bounds.width();
466         float maxScaleY = smallestSize / bounds.height();
467         float scale = Math.max(maxScaleX, maxScaleY);
468         float startScale = 1f;
469         if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
470             Drawable dr = ((BubbleTextView) v).getIcon();
471             if (dr instanceof FastBitmapDrawable) {
472                 startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
473             }
474         }
475         final float initialStartScale = startScale;
476 
477         int[] dragLayerBounds = new int[2];
478         mDragLayer.getLocationOnScreen(dragLayerBounds);
479 
480         // Animate the app icon to the center of the window bounds in screen coordinates.
481         float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
482         float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
483 
484         float dX = centerX - bounds.centerX();
485         float dY = centerY - bounds.centerY();
486 
487         boolean useUpwardAnimation = bounds.top > centerY
488                 || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
489         final long xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
490                 : APP_LAUNCH_DOWN_DURATION;
491         final long yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
492                 : APP_LAUNCH_DOWN_CURVED_DURATION;
493         final long alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
494                 : APP_LAUNCH_ALPHA_DOWN_DURATION;
495 
496         RectF targetBounds = new RectF(windowTargetBounds);
497         RectF iconBounds = new RectF();
498         RectF temp = new RectF();
499         Point tmpPos = new Point();
500 
501         AnimatorSet animatorSet = new AnimatorSet();
502         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
503         appAnimator.setDuration(APP_LAUNCH_DURATION);
504         appAnimator.setInterpolator(LINEAR);
505         appAnimator.addListener(floatingView);
506         appAnimator.addListener(new AnimatorListenerAdapter() {
507             @Override
508             public void onAnimationEnd(Animator animation) {
509                 if (v instanceof BubbleTextView) {
510                     ((BubbleTextView) v).setStayPressed(false);
511                 }
512                 openingTargets.release();
513             }
514         });
515 
516         float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION;
517 
518         final float startCrop;
519         final float endCrop;
520         if (mDeviceProfile.isVerticalBarLayout()) {
521             startCrop = windowTargetBounds.height();
522             endCrop = windowTargetBounds.width();
523         } else {
524             startCrop = windowTargetBounds.width();
525             endCrop = windowTargetBounds.height();
526         }
527 
528         final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
529                 ? startCrop / 2f : 0f;
530         final float windowRadius = mDeviceProfile.isMultiWindowMode
531                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
532         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
533             FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE);
534             FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE);
535             FloatProp mScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION,
536                     EXAGGERATED_EASE);
537             FloatProp mIconAlpha = new FloatProp(1f, 0f, APP_LAUNCH_ALPHA_START_DELAY,
538                     alphaDuration, LINEAR);
539             FloatProp mCroppedSize = new FloatProp(startCrop, endCrop, 0, CROP_DURATION,
540                     EXAGGERATED_EASE);
541             FloatProp mWindowRadius = new FloatProp(initialWindowRadius, windowRadius, 0,
542                     RADIUS_DURATION, EXAGGERATED_EASE);
543 
544             @Override
545             public void onUpdate(float percent) {
546                 // Calculate the size.
547                 float width = bounds.width() * mScale.value;
548                 float height = bounds.height() * mScale.value;
549 
550                 // Animate the crop so that it starts off as a square.
551                 final int cropWidth;
552                 final int cropHeight;
553                 if (mDeviceProfile.isVerticalBarLayout()) {
554                     cropWidth = (int) mCroppedSize.value;
555                     cropHeight = windowTargetBounds.height();
556                 } else {
557                     cropWidth = windowTargetBounds.width();
558                     cropHeight = (int) mCroppedSize.value;
559                 }
560                 crop.set(0, 0, cropWidth, cropHeight);
561 
562                 // Scale the size to match the crop.
563                 float scaleX = width / cropWidth;
564                 float scaleY = height / cropHeight;
565                 float scale = Math.min(1f, Math.max(scaleX, scaleY));
566 
567                 float scaledCropWidth = cropWidth * scale;
568                 float scaledCropHeight = cropHeight * scale;
569                 float offsetX  = (scaledCropWidth - width) / 2;
570                 float offsetY = (scaledCropHeight - height) / 2;
571 
572                 // Calculate the window position.
573                 temp.set(bounds);
574                 temp.offset(dragLayerBounds[0], dragLayerBounds[1]);
575                 temp.offset(mDx.value, mDy.value);
576                 Utilities.scaleRectFAboutCenter(temp, mScale.value);
577                 float windowTransX0 = temp.left - offsetX;
578                 float windowTransY0 = temp.top - offsetY;
579 
580                 // Calculate the icon position.
581                 iconBounds.set(bounds);
582                 iconBounds.offset(mDx.value, mDy.value);
583                 Utilities.scaleRectFAboutCenter(iconBounds, mScale.value);
584                 iconBounds.left -= offsetX;
585                 iconBounds.top -= offsetY;
586                 iconBounds.right += offsetX;
587                 iconBounds.bottom += offsetY;
588 
589                 float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale;
590                 float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale;
591                 SurfaceParams[] params = new SurfaceParams[appTargets.length];
592                 for (int i = appTargets.length - 1; i >= 0; i--) {
593                     RemoteAnimationTargetCompat target = appTargets[i];
594                     SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
595 
596                     if (target.mode == MODE_OPENING) {
597                         matrix.setScale(scale, scale);
598                         matrix.postTranslate(windowTransX0, windowTransY0);
599 
600                         floatingView.update(iconBounds, mIconAlpha.value, percent, 0f,
601                                 mWindowRadius.value * scale, true /* isOpening */);
602                         builder.withMatrix(matrix)
603                                 .withWindowCrop(crop)
604                                 .withAlpha(1f - mIconAlpha.value)
605                                 .withCornerRadius(mWindowRadius.value);
606                     } else {
607                         tmpPos.set(target.position.x, target.position.y);
608                         if (target.localBounds != null) {
609                             final Rect localBounds = target.localBounds;
610                             tmpPos.set(target.localBounds.left, target.localBounds.top);
611                         }
612 
613                         matrix.setTranslate(tmpPos.x, tmpPos.y);
614                         final Rect crop = new Rect(target.screenSpaceBounds);
615                         crop.offsetTo(0, 0);
616                         builder.withMatrix(matrix)
617                                 .withWindowCrop(crop)
618                                 .withAlpha(1f);
619                     }
620                     params[i] = builder.build();
621                 }
622                 surfaceApplier.scheduleApply(params);
623             }
624         });
625 
626         // When launching an app from overview that doesn't map to a task, we still want to just
627         // blur the wallpaper instead of the launcher surface as well
628         boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
629         DepthController depthController = mLauncher.getDepthController();
630         ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
631                 BACKGROUND_APP.getDepth(mLauncher))
632                 .setDuration(APP_LAUNCH_DURATION);
633         if (allowBlurringLauncher) {
634             depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
635                     appTargets, MODE_OPENING));
636             backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
637                 @Override
638                 public void onAnimationEnd(Animator animation) {
639                     depthController.setSurfaceToApp(null);
640                 }
641             });
642         }
643 
644         animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
645         return animatorSet;
646     }
647 
648     /**
649      * Registers remote animations used when closing apps to home screen.
650      */
651     @Override
652     public void registerRemoteAnimations() {
653         if (SEPARATE_RECENTS_ACTIVITY.get()) {
654             return;
655         }
656         if (hasControlRemoteAppTransitionPermission()) {
657             mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
658 
659             RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
660             definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
661                     WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
662                     new RemoteAnimationAdapterCompat(
663                             new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
664                                     false /* startAtFrontOfQueue */),
665                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
666 
667             if (KEYGUARD_ANIMATION.get()) {
668                 mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
669                 definition.addRemoteAnimation(
670                         WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
671                         new RemoteAnimationAdapterCompat(
672                                 new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
673                                         true /* startAtFrontOfQueue */),
674                                 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
675             }
676 
677             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
678         }
679     }
680 
681     /**
682      * Unregisters all remote animations.
683      */
684     @Override
685     public void unregisterRemoteAnimations() {
686         if (SEPARATE_RECENTS_ACTIVITY.get()) {
687             return;
688         }
689         if (hasControlRemoteAppTransitionPermission()) {
690             new ActivityCompat(mLauncher).unregisterRemoteAnimations();
691 
692             // Also clear strong references to the runners registered with the remote animation
693             // definition so we don't have to wait for the system gc
694             mWallpaperOpenRunner = null;
695             mAppLaunchRunner = null;
696             mKeyguardGoingAwayRunner = null;
697         }
698     }
699 
700     private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
701         return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
702     }
703 
704     /**
705      * @return Runner that plays when user goes to Launcher
706      *         ie. pressing home, swiping up from nav bar.
707      */
708     WrappedAnimationRunnerImpl createWallpaperOpenRunner(boolean fromUnlock) {
709         return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock);
710     }
711 
712     /**
713      * Animator that controls the transformations of the windows when unlocking the device.
714      */
715     private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets,
716             RemoteAnimationTargetCompat[] wallpaperTargets) {
717         SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
718         ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
719         unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
720         float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
721                 QuickStepContract.getWindowCornerRadius(mLauncher.getResources());
722         unlockAnimator.addListener(new AnimatorListenerAdapter() {
723             @Override
724             public void onAnimationStart(Animator animation) {
725                 SurfaceParams[] params = new SurfaceParams[appTargets.length];
726                 for (int i = appTargets.length - 1; i >= 0; i--) {
727                     RemoteAnimationTargetCompat target = appTargets[i];
728                     params[i] = new SurfaceParams.Builder(target.leash)
729                             .withAlpha(1f)
730                             .withWindowCrop(target.screenSpaceBounds)
731                             .withCornerRadius(cornerRadius)
732                             .build();
733                 }
734                 surfaceApplier.scheduleApply(params);
735             }
736         });
737         return unlockAnimator;
738     }
739 
740     /**
741      * Animator that controls the transformations of the windows the targets that are closing.
742      */
743     private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
744             RemoteAnimationTargetCompat[] wallpaperTargets) {
745         SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
746         Matrix matrix = new Matrix();
747         Point tmpPos = new Point();
748         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
749         int duration = CLOSING_TRANSITION_DURATION_MS;
750         float windowCornerRadius = mDeviceProfile.isMultiWindowMode
751                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
752         closingAnimator.setDuration(duration);
753         closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
754             FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
755             FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
756             FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
757 
758             @Override
759             public void onUpdate(float percent) {
760                 SurfaceParams[] params = new SurfaceParams[appTargets.length];
761                 for (int i = appTargets.length - 1; i >= 0; i--) {
762                     RemoteAnimationTargetCompat target = appTargets[i];
763                     SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
764 
765                     tmpPos.set(target.position.x, target.position.y);
766                     if (target.localBounds != null) {
767                         tmpPos.set(target.localBounds.left, target.localBounds.top);
768                     }
769 
770                     if (target.mode == MODE_CLOSING) {
771                         matrix.setScale(mScale.value, mScale.value,
772                                 target.screenSpaceBounds.centerX(),
773                                 target.screenSpaceBounds.centerY());
774                         matrix.postTranslate(0, mDy.value);
775                         matrix.postTranslate(tmpPos.x, tmpPos.y);
776                         builder.withMatrix(matrix)
777                                 .withAlpha(mAlpha.value)
778                                 .withCornerRadius(windowCornerRadius);
779                     } else {
780                         matrix.setTranslate(tmpPos.x, tmpPos.y);
781                         builder.withMatrix(matrix)
782                                 .withAlpha(1f);
783                     }
784                     final Rect crop = new Rect(target.screenSpaceBounds);
785                     crop.offsetTo(0, 0);
786                     params[i] = builder
787                             .withWindowCrop(crop)
788                             .build();
789                 }
790                 surfaceApplier.scheduleApply(params);
791             }
792         });
793 
794         return closingAnimator;
795     }
796 
797     private boolean hasControlRemoteAppTransitionPermission() {
798         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
799                 == PackageManager.PERMISSION_GRANTED;
800     }
801 
802     /**
803      * Remote animation runner for animation from the app to Launcher, including recents.
804      */
805     protected class WallpaperOpenLauncherAnimationRunner implements WrappedAnimationRunnerImpl {
806 
807         private final Handler mHandler;
808         private final boolean mFromUnlock;
809 
810         public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) {
811             mHandler = handler;
812             mFromUnlock = fromUnlock;
813         }
814 
815         @Override
816         public Handler getHandler() {
817             return mHandler;
818         }
819 
820         @Override
821         public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
822                 RemoteAnimationTargetCompat[] wallpaperTargets,
823                 LauncherAnimationRunner.AnimationResult result) {
824             if (mLauncher.isDestroyed()) {
825                 AnimatorSet anim = new AnimatorSet();
826                 anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets));
827                 result.setAnimation(anim, mLauncher.getApplicationContext());
828                 return;
829             }
830 
831             if (!mLauncher.hasBeenResumed()) {
832                 // If launcher is not resumed, wait until new async-frame after resume
833                 mLauncher.addOnResumeCallback(() ->
834                         postAsyncCallback(mHandler, () ->
835                                 onCreateAnimation(appTargets, wallpaperTargets, result)));
836                 return;
837             }
838 
839             if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
840                 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
841                 mLauncher.getStateManager().moveToRestState();
842             }
843 
844             AnimatorSet anim = null;
845             RemoteAnimationProvider provider = mRemoteAnimationProvider;
846             if (provider != null) {
847                 anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
848             }
849 
850             if (anim == null) {
851                 anim = new AnimatorSet();
852                 anim.play(mFromUnlock
853                         ? getUnlockWindowAnimator(appTargets, wallpaperTargets)
854                         : getClosingWindowAnimators(appTargets, wallpaperTargets));
855 
856                 // Normally, we run the launcher content animation when we are transitioning
857                 // home, but if home is already visible, then we don't want to animate the
858                 // contents of launcher unless we know that we are animating home as a result
859                 // of the home button press with quickstep, which will result in launcher being
860                 // started on touch down, prior to the animation home (and won't be in the
861                 // targets list because it is already visible). In that case, we force
862                 // invisibility on touch down, and only reset it after the animation to home
863                 // is initialized.
864                 if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
865                         || mLauncher.isForceInvisible()) {
866                     // Only register the content animation for cancellation when state changes
867                     mLauncher.getStateManager().setCurrentAnimation(anim);
868 
869                     if (mLauncher.isInState(LauncherState.ALL_APPS)) {
870                         Pair<AnimatorSet, Runnable> contentAnimator =
871                                 getLauncherContentAnimator(false /* isAppOpening */,
872                                         new float[] {-mContentTransY, 0});
873                         contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
874                         anim.play(contentAnimator.first);
875                         anim.addListener(new AnimatorListenerAdapter() {
876                             @Override
877                             public void onAnimationEnd(Animator animation) {
878                                 contentAnimator.second.run();
879                             }
880                         });
881                     } else {
882                         float velocityDpPerS = DynamicResource.provider(mLauncher)
883                                 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
884                         float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP,
885                                 velocityDpPerS, mLauncher.getResources().getDisplayMetrics());
886                         anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false)
887                                 .getAnimators());
888                     }
889                 }
890             }
891 
892             mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
893             result.setAnimation(anim, mLauncher);
894         }
895     }
896 
897     /**
898      * Remote animation runner for animation to launch an app.
899      */
900     private class AppLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
901 
902         private final Handler mHandler;
903         private final View mV;
904 
905         AppLaunchAnimationRunner(Handler handler, View v) {
906             mHandler = handler;
907             mV = v;
908         }
909 
910         @Override
911         public Handler getHandler() {
912             return mHandler;
913         }
914 
915         @Override
916         public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
917                 RemoteAnimationTargetCompat[] wallpaperTargets,
918                 LauncherAnimationRunner.AnimationResult result) {
919             AnimatorSet anim = new AnimatorSet();
920 
921             boolean launcherClosing =
922                     launcherIsATargetWithMode(appTargets, MODE_CLOSING);
923 
924             if (isLaunchingFromRecents(mV, appTargets)) {
925                 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
926                         launcherClosing);
927             } else {
928                 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
929                         launcherClosing);
930             }
931 
932             if (launcherClosing) {
933                 anim.addListener(mForceInvisibleListener);
934             }
935 
936             result.setAnimation(anim, mLauncher);
937         }
938     }
939 }
940