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