1 /*
2  * Copyright (C) 2021 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.wm.shell.transition;
18 
19 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
20 import static android.app.ActivityOptions.ANIM_CUSTOM;
21 import static android.app.ActivityOptions.ANIM_NONE;
22 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
23 import static android.app.ActivityOptions.ANIM_SCALE_UP;
24 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
25 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
26 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
27 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
28 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
29 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
30 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
31 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
32 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
33 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
34 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
35 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
36 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
37 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
38 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
39 import static android.view.WindowManager.TRANSIT_CHANGE;
40 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
41 import static android.view.WindowManager.TRANSIT_RELAUNCH;
42 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
43 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
44 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
45 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
46 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
47 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
48 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
49 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
50 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
51 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
52 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
53 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
54 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
55 
56 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
57 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
58 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
59 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
60 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
61 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
62 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
63 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
64 
65 import android.animation.Animator;
66 import android.animation.AnimatorListenerAdapter;
67 import android.animation.ValueAnimator;
68 import android.annotation.ColorInt;
69 import android.annotation.NonNull;
70 import android.annotation.Nullable;
71 import android.app.ActivityManager;
72 import android.app.ActivityThread;
73 import android.app.admin.DevicePolicyManager;
74 import android.content.BroadcastReceiver;
75 import android.content.Context;
76 import android.content.Intent;
77 import android.content.IntentFilter;
78 import android.graphics.Color;
79 import android.graphics.Insets;
80 import android.graphics.Point;
81 import android.graphics.Rect;
82 import android.graphics.drawable.Drawable;
83 import android.hardware.HardwareBuffer;
84 import android.os.Handler;
85 import android.os.IBinder;
86 import android.os.UserHandle;
87 import android.util.ArrayMap;
88 import android.view.Choreographer;
89 import android.view.SurfaceControl;
90 import android.view.SurfaceSession;
91 import android.view.WindowManager;
92 import android.view.animation.AlphaAnimation;
93 import android.view.animation.Animation;
94 import android.view.animation.Transformation;
95 import android.window.TransitionInfo;
96 import android.window.TransitionMetrics;
97 import android.window.TransitionRequestInfo;
98 import android.window.WindowContainerTransaction;
99 
100 import com.android.internal.R;
101 import com.android.internal.annotations.VisibleForTesting;
102 import com.android.internal.policy.ScreenDecorationsUtils;
103 import com.android.internal.policy.TransitionAnimation;
104 import com.android.internal.protolog.common.ProtoLog;
105 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
106 import com.android.wm.shell.common.DisplayController;
107 import com.android.wm.shell.common.DisplayLayout;
108 import com.android.wm.shell.common.ShellExecutor;
109 import com.android.wm.shell.common.TransactionPool;
110 import com.android.wm.shell.protolog.ShellProtoLogGroup;
111 import com.android.wm.shell.sysui.ShellInit;
112 import com.android.wm.shell.util.TransitionUtil;
113 
114 import java.util.ArrayList;
115 import java.util.List;
116 import java.util.function.Consumer;
117 
118 /** The default handler that handles anything not already handled. */
119 public class DefaultTransitionHandler implements Transitions.TransitionHandler {
120     private static final int MAX_ANIMATION_DURATION = 3000;
121 
122     private final TransactionPool mTransactionPool;
123     private final DisplayController mDisplayController;
124     private final Context mContext;
125     private final Handler mMainHandler;
126     private final ShellExecutor mMainExecutor;
127     private final ShellExecutor mAnimExecutor;
128     private final TransitionAnimation mTransitionAnimation;
129     private final DevicePolicyManager mDevicePolicyManager;
130 
131     private final SurfaceSession mSurfaceSession = new SurfaceSession();
132 
133     /** Keeps track of the currently-running animations associated with each transition. */
134     private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
135 
136     private final CounterRotatorHelper mRotator = new CounterRotatorHelper();
137     private final Rect mInsets = new Rect(0, 0, 0, 0);
138     private float mTransitionAnimationScaleSetting = 1.0f;
139 
140     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
141     private final int mCurrentUserId;
142 
143     private Drawable mEnterpriseThumbnailDrawable;
144 
145     private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
146         @Override
147         public void onReceive(Context context, Intent intent) {
148             if (intent.getIntExtra(EXTRA_RESOURCE_TYPE, /* default= */ -1)
149                     != EXTRA_RESOURCE_TYPE_DRAWABLE) {
150                 return;
151             }
152             updateEnterpriseThumbnailDrawable();
153         }
154     };
155 
DefaultTransitionHandler(@onNull Context context, @NonNull ShellInit shellInit, @NonNull DisplayController displayController, @NonNull TransactionPool transactionPool, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer)156     DefaultTransitionHandler(@NonNull Context context,
157             @NonNull ShellInit shellInit,
158             @NonNull DisplayController displayController,
159             @NonNull TransactionPool transactionPool,
160             @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
161             @NonNull ShellExecutor animExecutor,
162             @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
163         mDisplayController = displayController;
164         mTransactionPool = transactionPool;
165         mContext = context;
166         mMainHandler = mainHandler;
167         mMainExecutor = mainExecutor;
168         mAnimExecutor = animExecutor;
169         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
170         mCurrentUserId = UserHandle.myUserId();
171         mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
172         shellInit.addInitCallback(this::onInit, this);
173         mRootTDAOrganizer = rootTDAOrganizer;
174     }
175 
onInit()176     private void onInit() {
177         updateEnterpriseThumbnailDrawable();
178         mContext.registerReceiver(
179                 mEnterpriseResourceUpdatedReceiver,
180                 new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
181                 /* broadcastPermission = */ null,
182                 mMainHandler);
183 
184         TransitionAnimation.initAttributeCache(mContext, mMainHandler);
185     }
186 
updateEnterpriseThumbnailDrawable()187     private void updateEnterpriseThumbnailDrawable() {
188         mEnterpriseThumbnailDrawable = mDevicePolicyManager.getResources().getDrawable(
189                 WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
190                 () -> mContext.getDrawable(R.drawable.ic_corp_badge));
191     }
192 
193     @VisibleForTesting
getRotationAnimationHint(@onNull TransitionInfo.Change displayChange, @NonNull TransitionInfo info, @NonNull DisplayController displayController)194     static int getRotationAnimationHint(@NonNull TransitionInfo.Change displayChange,
195             @NonNull TransitionInfo info, @NonNull DisplayController displayController) {
196         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
197                 "Display is changing, resolve the animation hint.");
198         // The explicit request of display has the highest priority.
199         if (displayChange.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS) {
200             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
201                     "  display requests explicit seamless");
202             return ROTATION_ANIMATION_SEAMLESS;
203         }
204 
205         boolean allTasksSeamless = false;
206         boolean rejectSeamless = false;
207         ActivityManager.RunningTaskInfo topTaskInfo = null;
208         int animationHint = ROTATION_ANIMATION_ROTATE;
209         // Traverse in top-to-bottom order so that the first task is top-most.
210         final int size = info.getChanges().size();
211         for (int i = 0; i < size; ++i) {
212             final TransitionInfo.Change change = info.getChanges().get(i);
213 
214             // Only look at changing things. showing/hiding don't need to rotate.
215             if (change.getMode() != TRANSIT_CHANGE) continue;
216 
217             // This container isn't rotating, so we can ignore it.
218             if (change.getEndRotation() == change.getStartRotation()) continue;
219             if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
220                 // In the presence of System Alert windows we can not seamlessly rotate.
221                 if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
222                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
223                             "  display has system alert windows, so not seamless.");
224                     rejectSeamless = true;
225                 }
226             } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
227                 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
228                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
229                             "  wallpaper is participating but isn't seamless.");
230                     rejectSeamless = true;
231                 }
232             } else if (change.getTaskInfo() != null) {
233                 final int anim = change.getRotationAnimation();
234                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
235                 final boolean isTopTask = topTaskInfo == null;
236                 if (isTopTask) {
237                     topTaskInfo = taskInfo;
238                     if (anim != ROTATION_ANIMATION_UNSPECIFIED
239                             && anim != ROTATION_ANIMATION_SEAMLESS) {
240                         animationHint = anim;
241                     }
242                 }
243                 // We only enable seamless rotation if all the visible task windows requested it.
244                 if (anim != ROTATION_ANIMATION_SEAMLESS) {
245                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
246                             "  task %s isn't requesting seamless, so not seamless.",
247                             taskInfo.taskId);
248                     allTasksSeamless = false;
249                 } else if (isTopTask) {
250                     allTasksSeamless = true;
251                 }
252             }
253         }
254 
255         if (!allTasksSeamless || rejectSeamless) {
256             return animationHint;
257         }
258 
259         // This is the only way to get display-id currently, so check display capabilities here.
260         final DisplayLayout displayLayout = displayController.getDisplayLayout(
261                 topTaskInfo.displayId);
262         // This condition should be true when using gesture navigation or the screen size is large
263         // (>600dp) because the bar is small relative to screen.
264         if (displayLayout.allowSeamlessRotationDespiteNavBarMoving()) {
265             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  nav bar allows seamless.");
266             return ROTATION_ANIMATION_SEAMLESS;
267         }
268         // For the upside down rotation we don't rotate seamlessly as the navigation bar moves
269         // position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
270         // will not enter the reverse portrait orientation, so actually the orientation won't
271         // change at all.
272         final int upsideDownRotation = displayLayout.getUpsideDownRotation();
273         if (displayChange.getStartRotation() == upsideDownRotation
274                 || displayChange.getEndRotation() == upsideDownRotation) {
275             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
276                     "  rotation involves upside-down portrait, so not seamless.");
277             return animationHint;
278         }
279 
280         // If the navigation bar cannot change sides, then it will jump when changing orientation
281         // so do not use seamless rotation.
282         if (!displayLayout.navigationBarCanMove()) {
283             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
284                     "  nav bar changes sides, so not seamless.");
285             return animationHint;
286         }
287         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
288         return ROTATION_ANIMATION_SEAMLESS;
289     }
290 
291     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)292     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
293             @NonNull SurfaceControl.Transaction startTransaction,
294             @NonNull SurfaceControl.Transaction finishTransaction,
295             @NonNull Transitions.TransitionFinishCallback finishCallback) {
296         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
297                 "start default transition animation, info = %s", info);
298         // If keyguard goes away, we should loadKeyguardExitAnimation. Otherwise this just
299         // immediately finishes since there is no animation for screen-wake.
300         if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
301             startTransaction.apply();
302             finishCallback.onTransitionFinished(null /* wct */);
303             return true;
304         }
305 
306         // Early check if the transition doesn't warrant an animation.
307         if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)
308                 || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) {
309             startTransaction.apply();
310             finishTransaction.apply();
311             finishCallback.onTransitionFinished(null /* wct */);
312             return true;
313         }
314 
315         if (mAnimations.containsKey(transition)) {
316             throw new IllegalStateException("Got a duplicate startAnimation call for "
317                     + transition);
318         }
319         final ArrayList<Animator> animations = new ArrayList<>();
320         mAnimations.put(transition, animations);
321 
322         final Runnable onAnimFinish = () -> {
323             if (!animations.isEmpty()) return;
324             mAnimations.remove(transition);
325             finishCallback.onTransitionFinished(null /* wct */);
326         };
327 
328         final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
329                 new ArrayList<>();
330 
331         @ColorInt int backgroundColorForTransition = 0;
332         final int wallpaperTransit = getWallpaperTransitType(info);
333         boolean isDisplayRotationAnimationStarted = false;
334         final boolean isDreamTransition = isDreamTransition(info);
335         final boolean isOnlyTranslucent = isOnlyTranslucent(info);
336 
337         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
338             final TransitionInfo.Change change = info.getChanges().get(i);
339             if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
340                     | FLAG_IS_BEHIND_STARTING_WINDOW)) {
341                 // Don't animate embedded activity if it is covered by the starting window.
342                 // Non-embedded case still needs animation because the container can still animate
343                 // the starting window together, e.g. CLOSE or CHANGE type.
344                 continue;
345             }
346             if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
347                 // Wallpaper, IME, and system windows don't need any default animations.
348                 continue;
349             }
350             final boolean isTask = change.getTaskInfo() != null;
351             final int mode = change.getMode();
352             boolean isSeamlessDisplayChange = false;
353 
354             if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
355                 if (info.getType() == TRANSIT_CHANGE) {
356                     final int anim = getRotationAnimationHint(change, info, mDisplayController);
357                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
358                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
359                         startRotationAnimation(startTransaction, change, info, anim, animations,
360                                 onAnimFinish);
361                         isDisplayRotationAnimationStarted = true;
362                         continue;
363                     }
364                 } else {
365                     // Opening/closing an app into a new orientation.
366                     mRotator.handleClosingChanges(info, startTransaction, change);
367                 }
368             }
369 
370             if (mode == TRANSIT_CHANGE) {
371                 // If task is child task, only set position in parent and update crop when needed.
372                 if (isTask && change.getParent() != null
373                         && info.getChange(change.getParent()).getTaskInfo() != null) {
374                     final Point positionInParent = change.getTaskInfo().positionInParent;
375                     startTransaction.setPosition(change.getLeash(),
376                             positionInParent.x, positionInParent.y);
377 
378                     if (!change.getEndAbsBounds().equals(
379                             info.getChange(change.getParent()).getEndAbsBounds())) {
380                         startTransaction.setWindowCrop(change.getLeash(),
381                                 change.getEndAbsBounds().width(),
382                                 change.getEndAbsBounds().height());
383                     }
384 
385                     continue;
386                 }
387 
388                 // There is no default animation for Pip window in rotation transition, and the
389                 // PipTransition will update the surface of its own window at start/finish.
390                 if (isTask && change.getTaskInfo().configuration.windowConfiguration
391                         .getWindowingMode() == WINDOWING_MODE_PINNED) {
392                     continue;
393                 }
394                 // No default animation for this, so just update bounds/position.
395                 final int rootIdx = TransitionUtil.rootIndexFor(change, info);
396                 startTransaction.setPosition(change.getLeash(),
397                         change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
398                         change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
399                 // Seamless display transition doesn't need to animate.
400                 if (isSeamlessDisplayChange) continue;
401                 if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
402                         && !change.hasFlags(FLAG_FILLS_TASK))) {
403                     // Update Task and embedded split window crop bounds, otherwise we may see crop
404                     // on previous bounds during the rotation animation.
405                     startTransaction.setWindowCrop(change.getLeash(),
406                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
407                 }
408                 // Rotation change of independent non display window container.
409                 if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
410                         && change.getStartRotation() != change.getEndRotation()) {
411                     startRotationAnimation(startTransaction, change, info,
412                             ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
413                     continue;
414                 }
415             }
416 
417             // Hide the invisible surface directly without animating it if there is a display
418             // rotation animation playing.
419             if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) {
420                 startTransaction.hide(change.getLeash());
421                 continue;
422             }
423 
424             // The back gesture has animated this change before transition happen, so here we don't
425             // play the animation again.
426             if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
427                 continue;
428             }
429             // Don't animate anything that isn't independent.
430             if (!TransitionInfo.isIndependent(change, info)) continue;
431 
432             Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
433             if (a != null) {
434                 if (isTask) {
435                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
436                     if (!isTranslucent && TransitionUtil.isOpenOrCloseMode(mode)
437                             && TransitionUtil.isOpenOrCloseMode(info.getType())
438                             && wallpaperTransit == WALLPAPER_TRANSITION_NONE) {
439                         // Use the overview background as the background for the animation
440                         final Context uiContext = ActivityThread.currentActivityThread()
441                                 .getSystemUiContext();
442                         backgroundColorForTransition =
443                                 uiContext.getColor(R.color.overview_background);
444                     }
445                     if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN
446                             && TransitionUtil.isOpeningType(info.getType())) {
447                         // Need to flip the z-order of opening/closing because the WALLPAPER_OPEN
448                         // always animates the closing task over the opening one while
449                         // traditionally, an OPEN transition animates the opening over the closing.
450 
451                         // See Transitions#setupAnimHierarchy for details about these variables.
452                         final int numChanges = info.getChanges().size();
453                         final int zSplitLine = numChanges + 1;
454                         if (TransitionUtil.isOpeningType(mode)) {
455                             final int layer = zSplitLine - i;
456                             startTransaction.setLayer(change.getLeash(), layer);
457                         } else if (TransitionUtil.isClosingType(mode)) {
458                             final int layer = zSplitLine + numChanges - i;
459                             startTransaction.setLayer(change.getLeash(), layer);
460                         }
461                     } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
462                                 && TransitionUtil.isClosingType(mode)) {
463                         // If there is a closing translucent task in an OPENING transition, we will
464                         // actually select a CLOSING animation, so move the closing task into
465                         // the animating part of the z-order.
466 
467                         // See Transitions#setupAnimHierarchy for details about these variables.
468                         final int numChanges = info.getChanges().size();
469                         final int zSplitLine = numChanges + 1;
470                         final int layer = zSplitLine + numChanges - i;
471                         startTransaction.setLayer(change.getLeash(), layer);
472                     }
473                 }
474 
475                 final float cornerRadius;
476                 if (a.hasRoundedCorners() && isTask) {
477                     // hasRoundedCorners is currently only enabled for tasks
478                     final Context displayContext =
479                             mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
480                     cornerRadius = displayContext == null ? 0
481                             : ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
482                 } else {
483                     cornerRadius = 0;
484                 }
485 
486                 backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
487                         backgroundColorForTransition);
488 
489                 if (!isTask && a.hasExtension()) {
490                     if (!TransitionUtil.isOpeningType(mode)) {
491                         // Can screenshot now (before startTransaction is applied)
492                         edgeExtendWindow(change, a, startTransaction, finishTransaction);
493                     } else {
494                         // Need to screenshot after startTransaction is applied otherwise activity
495                         // may not be visible or ready yet.
496                         postStartTransactionCallbacks
497                                 .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
498                     }
499                 }
500 
501                 final Rect clipRect = TransitionUtil.isClosingType(mode)
502                         ? new Rect(mRotator.getEndBoundsInStartRotation(change))
503                         : new Rect(change.getEndAbsBounds());
504                 clipRect.offsetTo(0, 0);
505 
506                 buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
507                         mTransactionPool, mMainExecutor, change.getEndRelOffset(), cornerRadius,
508                         clipRect);
509 
510                 if (info.getAnimationOptions() != null) {
511                     attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
512                             cornerRadius);
513                 }
514             }
515         }
516 
517         if (backgroundColorForTransition != 0) {
518             addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
519                     finishTransaction);
520         }
521 
522         if (postStartTransactionCallbacks.size() > 0) {
523             // postStartTransactionCallbacks require that the start transaction is already
524             // applied to run otherwise they may result in flickers and UI inconsistencies.
525             startTransaction.apply(true /* sync */);
526             // startTransaction is empty now, so fill it with the edge-extension setup
527             for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
528                     postStartTransactionCallbacks) {
529                 postStartTransactionCallback.accept(startTransaction);
530             }
531         }
532         startTransaction.apply();
533 
534         // now start animations. they are started on another thread, so we have to post them
535         // *after* applying the startTransaction
536         mAnimExecutor.execute(() -> {
537             for (int i = 0; i < animations.size(); ++i) {
538                 animations.get(i).start();
539             }
540         });
541 
542         mRotator.cleanUp(finishTransaction);
543         TransitionMetrics.getInstance().reportAnimationStart(transition);
544         // run finish now in-case there are no animations
545         onAnimFinish.run();
546         return true;
547     }
548 
addBackgroundColorOnTDA(@onNull TransitionInfo info, @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)549     private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
550             @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
551             @NonNull SurfaceControl.Transaction finishTransaction) {
552         final Color bgColor = Color.valueOf(color);
553         final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
554 
555         for (int i = 0; i < info.getRootCount(); ++i) {
556             final int displayId = info.getRoot(i).getDisplayId();
557             final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
558                     .setName("animation-background")
559                     .setCallsite("DefaultTransitionHandler")
560                     .setColorLayer();
561 
562             mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
563             final SurfaceControl backgroundSurface = colorLayerBuilder.build();
564             startTransaction.setColor(backgroundSurface, colorArray)
565                     .setLayer(backgroundSurface, -1)
566                     .show(backgroundSurface);
567             finishTransaction.remove(backgroundSurface);
568         }
569     }
570 
isDreamTransition(@onNull TransitionInfo info)571     private static boolean isDreamTransition(@NonNull TransitionInfo info) {
572         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
573             final TransitionInfo.Change change = info.getChanges().get(i);
574             if (change.getTaskInfo() != null
575                     && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM) {
576                 return true;
577             }
578         }
579 
580         return false;
581     }
582 
583     /**
584      * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select
585      * different animations and z-orders for these
586      */
isOnlyTranslucent(@onNull TransitionInfo info)587     private static boolean isOnlyTranslucent(@NonNull TransitionInfo info) {
588         int translucentOpen = 0;
589         int translucentClose = 0;
590         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
591             final TransitionInfo.Change change = info.getChanges().get(i);
592             if (change.getMode() == TRANSIT_CHANGE) continue;
593             if (change.hasFlags(FLAG_TRANSLUCENT)) {
594                 if (TransitionUtil.isOpeningType(change.getMode())) {
595                     translucentOpen += 1;
596                 } else {
597                     translucentClose += 1;
598                 }
599             } else {
600                 return false;
601             }
602         }
603         return (translucentOpen + translucentClose) > 0;
604     }
605 
606     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)607     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
608             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
609             @NonNull Transitions.TransitionFinishCallback finishCallback) {
610         ArrayList<Animator> anims = mAnimations.get(mergeTarget);
611         if (anims == null) return;
612         for (int i = anims.size() - 1; i >= 0; --i) {
613             final Animator anim = anims.get(i);
614             mAnimExecutor.execute(anim::end);
615         }
616     }
617 
startRotationAnimation(SurfaceControl.Transaction startTransaction, TransitionInfo.Change change, TransitionInfo info, int animHint, ArrayList<Animator> animations, Runnable onAnimFinish)618     private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
619             TransitionInfo.Change change, TransitionInfo info, int animHint,
620             ArrayList<Animator> animations, Runnable onAnimFinish) {
621         final int rootIdx = TransitionUtil.rootIndexFor(change, info);
622         final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
623                 mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
624                 animHint);
625         // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
626         // content, and background color. The item of "animGroup" will be removed if the sub
627         // animation is finished. Then if the list becomes empty, the rotation animation is done.
628         final ArrayList<Animator> animGroup = new ArrayList<>(3);
629         final ArrayList<Animator> animGroupStore = new ArrayList<>(3);
630         final Runnable finishCallback = () -> {
631             if (!animGroup.isEmpty()) return;
632             anim.kill();
633             animations.removeAll(animGroupStore);
634             onAnimFinish.run();
635         };
636         anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
637                 mMainExecutor);
638         for (int i = animGroup.size() - 1; i >= 0; i--) {
639             final Animator animator = animGroup.get(i);
640             animGroupStore.add(animator);
641             animations.add(animator);
642         }
643     }
644 
645     @Nullable
646     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)647     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
648             @NonNull TransitionRequestInfo request) {
649         return null;
650     }
651 
652     @Override
setAnimScaleSetting(float scale)653     public void setAnimScaleSetting(float scale) {
654         mTransitionAnimationScaleSetting = scale;
655     }
656 
657     @Nullable
loadAnimation(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, boolean isDreamTransition)658     private Animation loadAnimation(@NonNull TransitionInfo info,
659             @NonNull TransitionInfo.Change change, int wallpaperTransit,
660             boolean isDreamTransition) {
661         Animation a;
662 
663         final int type = info.getType();
664         final int flags = info.getFlags();
665         final int changeMode = change.getMode();
666         final int changeFlags = change.getFlags();
667         final boolean isOpeningType = TransitionUtil.isOpeningType(type);
668         final boolean enter = TransitionUtil.isOpeningType(changeMode);
669         final boolean isTask = change.getTaskInfo() != null;
670         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
671         final int overrideType = options != null ? options.getType() : ANIM_NONE;
672         final Rect endBounds = TransitionUtil.isClosingType(changeMode)
673                 ? mRotator.getEndBoundsInStartRotation(change)
674                 : change.getEndAbsBounds();
675 
676         if (info.isKeyguardGoingAway()) {
677             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
678                     (changeFlags & FLAG_SHOW_WALLPAPER) != 0);
679         } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
680             a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
681         } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
682             if (isOpeningType) {
683                 a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
684             } else {
685                 a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
686             }
687         } else if (changeMode == TRANSIT_CHANGE) {
688             // In the absence of a specific adapter, we just want to keep everything stationary.
689             a = new AlphaAnimation(1.f, 1.f);
690             a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
691         } else if (type == TRANSIT_RELAUNCH) {
692             a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds);
693         } else if (overrideType == ANIM_CUSTOM
694                 && (!isTask || options.getOverrideTaskTransition())) {
695             a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
696                     ? options.getEnterResId() : options.getExitResId());
697         } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
698             a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
699         } else if (overrideType == ANIM_CLIP_REVEAL) {
700             a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
701                     endBounds, endBounds, options.getTransitionBounds());
702         } else if (overrideType == ANIM_SCALE_UP) {
703             a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter,
704                     endBounds, options.getTransitionBounds());
705         } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP
706                 || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) {
707             final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP;
708             a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
709                     endBounds, type, wallpaperTransit, options.getThumbnail(),
710                     options.getTransitionBounds());
711         } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
712             // This received a transferred starting window, so don't animate
713             return null;
714         } else if (overrideType == ANIM_SCENE_TRANSITION) {
715             // If there's a scene-transition, then jump-cut.
716             return null;
717         } else {
718             a = loadAttributeAnimation(
719                     info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
720         }
721 
722         if (a != null) {
723             if (!a.isInitialized()) {
724                 final Rect animationRange = TransitionUtil.isClosingType(changeMode)
725                         ? change.getStartAbsBounds() : change.getEndAbsBounds();
726                 a.initialize(animationRange.width(), animationRange.height(),
727                         endBounds.width(), endBounds.height());
728             }
729             a.restrictDuration(MAX_ANIMATION_DURATION);
730             a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
731         }
732         return a;
733     }
734 
735     /** Builds an animator for the surface and adds it to the `animations` list. */
buildSurfaceAnimation(@onNull ArrayList<Animator> animations, @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, @Nullable Rect clipRect)736     static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
737             @NonNull Animation anim, @NonNull SurfaceControl leash,
738             @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
739             @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
740             @Nullable Rect clipRect) {
741         final SurfaceControl.Transaction transaction = pool.acquire();
742         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
743         final Transformation transformation = new Transformation();
744         final float[] matrix = new float[9];
745         // Animation length is already expected to be scaled.
746         va.overrideDurationScale(1.0f);
747         va.setDuration(anim.computeDurationHint());
748         final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
749             final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
750 
751             applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
752                     position, cornerRadius, clipRect);
753         };
754         va.addUpdateListener(updateListener);
755 
756         final Runnable finisher = () -> {
757             applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
758                     position, cornerRadius, clipRect);
759 
760             pool.release(transaction);
761             mainExecutor.execute(() -> {
762                 animations.remove(va);
763                 finishCallback.run();
764             });
765         };
766         va.addListener(new AnimatorListenerAdapter() {
767             // It is possible for the end/cancel to be called more than once, which may cause
768             // issues if the animating surface has already been released. Track the finished
769             // state here to skip duplicate callbacks. See b/252872225.
770             private boolean mFinished = false;
771 
772             @Override
773             public void onAnimationEnd(Animator animation) {
774                 onFinish();
775             }
776 
777             @Override
778             public void onAnimationCancel(Animator animation) {
779                 onFinish();
780             }
781 
782             private void onFinish() {
783                 if (mFinished) return;
784                 mFinished = true;
785                 finisher.run();
786                 // The update listener can continue to be called after the animation has ended if
787                 // end() is called manually again before the finisher removes the animation.
788                 // Remove it manually here to prevent animating a released surface.
789                 // See b/252872225.
790                 va.removeUpdateListener(updateListener);
791             }
792         });
793         animations.add(va);
794     }
795 
attachThumbnail(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius)796     private void attachThumbnail(@NonNull ArrayList<Animator> animations,
797             @NonNull Runnable finishCallback, TransitionInfo.Change change,
798             TransitionInfo.AnimationOptions options, float cornerRadius) {
799         final boolean isOpen = TransitionUtil.isOpeningType(change.getMode());
800         final boolean isClose = TransitionUtil.isClosingType(change.getMode());
801         if (isOpen) {
802             if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
803                 attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
804                         cornerRadius);
805             } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
806                 attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
807             }
808         } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
809             attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
810         }
811     }
812 
attachCrossProfileThumbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius)813     private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations,
814             @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
815         final Rect bounds = change.getEndAbsBounds();
816         // Show the right drawable depending on the user we're transitioning to.
817         final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
818                         ? mContext.getDrawable(R.drawable.ic_account_circle)
819                         : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
820                                 ? mEnterpriseThumbnailDrawable : null;
821         if (thumbnailDrawable == null) {
822             return;
823         }
824         final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
825                 thumbnailDrawable, bounds);
826         if (thumbnail == null) {
827             return;
828         }
829 
830         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
831         final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
832                 change.getLeash(), thumbnail, transaction);
833         final Animation a =
834                 mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds);
835         if (a == null) {
836             return;
837         }
838 
839         final Runnable finisher = () -> {
840             wt.destroy(transaction);
841             mTransactionPool.release(transaction);
842 
843             finishCallback.run();
844         };
845         a.restrictDuration(MAX_ANIMATION_DURATION);
846         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
847         buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
848                 mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
849     }
850 
attachThumbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius)851     private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
852             @NonNull Runnable finishCallback, TransitionInfo.Change change,
853             TransitionInfo.AnimationOptions options, float cornerRadius) {
854         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
855         final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
856                 change.getLeash(), options.getThumbnail(), transaction);
857         final Rect bounds = change.getEndAbsBounds();
858         final int orientation = mContext.getResources().getConfiguration().orientation;
859         final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds,
860                 mInsets, options.getThumbnail(), orientation, null /* startRect */,
861                 options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP);
862 
863         final Runnable finisher = () -> {
864             wt.destroy(transaction);
865             mTransactionPool.release(transaction);
866 
867             finishCallback.run();
868         };
869         a.restrictDuration(MAX_ANIMATION_DURATION);
870         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
871         buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
872                 mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
873     }
874 
getWallpaperTransitType(TransitionInfo info)875     private static int getWallpaperTransitType(TransitionInfo info) {
876         boolean hasOpenWallpaper = false;
877         boolean hasCloseWallpaper = false;
878 
879         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
880             final TransitionInfo.Change change = info.getChanges().get(i);
881             if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
882                 if (TransitionUtil.isOpeningType(change.getMode())) {
883                     hasOpenWallpaper = true;
884                 } else if (TransitionUtil.isClosingType(change.getMode())) {
885                     hasCloseWallpaper = true;
886                 }
887             }
888         }
889 
890         if (hasOpenWallpaper && hasCloseWallpaper) {
891             return TransitionUtil.isOpeningType(info.getType())
892                     ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
893         } else if (hasOpenWallpaper) {
894             return WALLPAPER_TRANSITION_OPEN;
895         } else if (hasCloseWallpaper) {
896             return WALLPAPER_TRANSITION_CLOSE;
897         } else {
898             return WALLPAPER_TRANSITION_NONE;
899         }
900     }
901 
902     /**
903      * Returns {@code true} if the default transition handler can run the override animation.
904      * @see #loadAnimation(TransitionInfo, TransitionInfo.Change, int, boolean)
905      */
isSupportedOverrideAnimation( @onNull TransitionInfo.AnimationOptions options)906     public static boolean isSupportedOverrideAnimation(
907             @NonNull TransitionInfo.AnimationOptions options) {
908         final int animType = options.getType();
909         return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP
910                 || animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN
911                 || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS;
912     }
913 
applyTransformation(long time, SurfaceControl.Transaction t, SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix, Point position, float cornerRadius, @Nullable Rect immutableClipRect)914     private static void applyTransformation(long time, SurfaceControl.Transaction t,
915             SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
916             Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
917         tmpTransformation.clear();
918         anim.getTransformation(time, tmpTransformation);
919         if (position != null) {
920             tmpTransformation.getMatrix().postTranslate(position.x, position.y);
921         }
922         t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
923         t.setAlpha(leash, tmpTransformation.getAlpha());
924 
925         final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
926         Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
927         if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
928             // Clip out any overflowing edge extension
929             clipRect.inset(extensionInsets);
930             t.setCrop(leash, clipRect);
931         }
932 
933         if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
934             // We can only apply rounded corner if a crop is set
935             t.setCrop(leash, clipRect);
936             t.setCornerRadius(leash, cornerRadius);
937         }
938 
939         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
940         t.apply();
941     }
942 }
943