• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LauncherAppTransitionManagerImpl(Context context)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
onDeviceProfileChanged(DeviceProfile dp)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
getActivityLaunchOptions(Launcher launcher, View v)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      */
getWindowTargetBounds(RemoteAnimationTargetCompat[] targets)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 
setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, CancellationSignal cancellationSignal)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      */
composeRecentsLaunchAnimator(View v, RemoteAnimationTargetCompat[] targets, AnimatorSet target)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      */
getLauncherContentAnimator(boolean isAppOpening)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      */
getIconAnimator(View v, Rect windowTargetBounds)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();
getClosingWindowAnimators(targetCompats)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 
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL)698                 mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
result.setAnimation(anim)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