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_THUMBNAIL_SCALE_DOWN;
25 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
26 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
27 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
28 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
29 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
30 import static android.view.WindowManager.TRANSIT_CHANGE;
31 import static android.view.WindowManager.TRANSIT_CLOSE;
32 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
33 import static android.view.WindowManager.TRANSIT_OPEN;
34 import static android.view.WindowManager.TRANSIT_RELAUNCH;
35 import static android.view.WindowManager.TRANSIT_TO_BACK;
36 import static android.view.WindowManager.TRANSIT_TO_FRONT;
37 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
38 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
39 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
40 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
41 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
42 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
43 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
44 import static android.window.TransitionInfo.isIndependent;
45 
46 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
47 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
48 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
49 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
50 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
51 
52 import android.animation.Animator;
53 import android.animation.AnimatorListenerAdapter;
54 import android.animation.ValueAnimator;
55 import android.annotation.NonNull;
56 import android.annotation.Nullable;
57 import android.content.Context;
58 import android.graphics.Point;
59 import android.graphics.Rect;
60 import android.hardware.HardwareBuffer;
61 import android.os.IBinder;
62 import android.os.SystemProperties;
63 import android.os.UserHandle;
64 import android.util.ArrayMap;
65 import android.view.Choreographer;
66 import android.view.SurfaceControl;
67 import android.view.SurfaceSession;
68 import android.view.WindowManager;
69 import android.view.animation.AlphaAnimation;
70 import android.view.animation.Animation;
71 import android.view.animation.Transformation;
72 import android.window.TransitionInfo;
73 import android.window.TransitionMetrics;
74 import android.window.TransitionRequestInfo;
75 import android.window.WindowContainerToken;
76 import android.window.WindowContainerTransaction;
77 
78 import com.android.internal.R;
79 import com.android.internal.annotations.VisibleForTesting;
80 import com.android.internal.policy.AttributeCache;
81 import com.android.internal.policy.TransitionAnimation;
82 import com.android.internal.protolog.common.ProtoLog;
83 import com.android.wm.shell.common.DisplayController;
84 import com.android.wm.shell.common.DisplayLayout;
85 import com.android.wm.shell.common.ShellExecutor;
86 import com.android.wm.shell.common.TransactionPool;
87 import com.android.wm.shell.protolog.ShellProtoLogGroup;
88 import com.android.wm.shell.util.CounterRotator;
89 
90 import java.util.ArrayList;
91 
92 /** The default handler that handles anything not already handled. */
93 public class DefaultTransitionHandler implements Transitions.TransitionHandler {
94     private static final int MAX_ANIMATION_DURATION = 3000;
95 
96     /**
97      * Restrict ability of activities overriding transition animation in a way such that
98      * an activity can do it only when the transition happens within a same task.
99      *
100      * @see android.app.Activity#overridePendingTransition(int, int)
101      */
102     private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
103             "persist.wm.disable_custom_task_animation";
104 
105     /**
106      * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
107      */
108     static boolean sDisableCustomTaskAnimationProperty =
109             SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
110 
111     private final TransactionPool mTransactionPool;
112     private final DisplayController mDisplayController;
113     private final Context mContext;
114     private final ShellExecutor mMainExecutor;
115     private final ShellExecutor mAnimExecutor;
116     private final TransitionAnimation mTransitionAnimation;
117 
118     private final SurfaceSession mSurfaceSession = new SurfaceSession();
119 
120     /** Keeps track of the currently-running animations associated with each transition. */
121     private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
122 
123     private final Rect mInsets = new Rect(0, 0, 0, 0);
124     private float mTransitionAnimationScaleSetting = 1.0f;
125 
126     private final int mCurrentUserId;
127 
128     private ScreenRotationAnimation mRotationAnimation;
129 
DefaultTransitionHandler(@onNull DisplayController displayController, @NonNull TransactionPool transactionPool, Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor)130     DefaultTransitionHandler(@NonNull DisplayController displayController,
131             @NonNull TransactionPool transactionPool, Context context,
132             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
133         mDisplayController = displayController;
134         mTransactionPool = transactionPool;
135         mContext = context;
136         mMainExecutor = mainExecutor;
137         mAnimExecutor = animExecutor;
138         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
139         mCurrentUserId = UserHandle.myUserId();
140 
141         AttributeCache.init(context);
142     }
143 
144     @VisibleForTesting
isRotationSeamless(@onNull TransitionInfo info, DisplayController displayController)145     static boolean isRotationSeamless(@NonNull TransitionInfo info,
146             DisplayController displayController) {
147         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
148                 "Display is changing, check if it should be seamless.");
149         boolean checkedDisplayLayout = false;
150         boolean hasTask = false;
151         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
152             final TransitionInfo.Change change = info.getChanges().get(i);
153 
154             // Only look at changing things. showing/hiding don't need to rotate.
155             if (change.getMode() != TRANSIT_CHANGE) continue;
156 
157             // This container isn't rotating, so we can ignore it.
158             if (change.getEndRotation() == change.getStartRotation()) continue;
159 
160             if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
161                 // In the presence of System Alert windows we can not seamlessly rotate.
162                 if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
163                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
164                             "  display has system alert windows, so not seamless.");
165                     return false;
166                 }
167             } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
168                 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
169                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
170                             "  wallpaper is participating but isn't seamless.");
171                     return false;
172                 }
173             } else if (change.getTaskInfo() != null) {
174                 hasTask = true;
175                 // We only enable seamless rotation if all the visible task windows requested it.
176                 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
177                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
178                             "  task %s isn't requesting seamless, so not seamless.",
179                             change.getTaskInfo().taskId);
180                     return false;
181                 }
182 
183                 // This is the only way to get display-id currently, so we will check display
184                 // capabilities here
185                 if (!checkedDisplayLayout) {
186                     // only need to check display once.
187                     checkedDisplayLayout = true;
188                     final DisplayLayout displayLayout = displayController.getDisplayLayout(
189                             change.getTaskInfo().displayId);
190                     // For the upside down rotation we don't rotate seamlessly as the navigation
191                     // bar moves position. Note most apps (using orientation:sensor or user as
192                     // opposed to fullSensor) will not enter the reverse portrait orientation, so
193                     // actually the orientation won't change at all.
194                     int upsideDownRotation = displayLayout.getUpsideDownRotation();
195                     if (change.getStartRotation() == upsideDownRotation
196                             || change.getEndRotation() == upsideDownRotation) {
197                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
198                                 "  rotation involves upside-down portrait, so not seamless.");
199                         return false;
200                     }
201 
202                     // If the navigation bar can't change sides, then it will jump when we change
203                     // orientations and we don't rotate seamlessly - unless that is allowed, eg.
204                     // with gesture navigation where the navbar is low-profile enough that this
205                     // isn't very noticeable.
206                     if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
207                             && (!(displayLayout.navigationBarCanMove()
208                                     && (change.getStartAbsBounds().width()
209                                             != change.getStartAbsBounds().height())))) {
210                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
211                                 "  nav bar changes sides, so not seamless.");
212                         return false;
213                     }
214                 }
215             }
216         }
217 
218         // ROTATION_ANIMATION_SEAMLESS can only be requested by task.
219         if (hasTask) {
220             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
221             return true;
222         }
223         return false;
224     }
225 
226     /**
227      * Gets the rotation animation for the topmost task. Assumes that seamless is checked
228      * elsewhere, so it will default SEAMLESS to ROTATE.
229      */
getRotationAnimation(@onNull TransitionInfo info)230     private int getRotationAnimation(@NonNull TransitionInfo info) {
231         // Traverse in top-to-bottom order so that the first task is top-most
232         for (int i = 0; i < info.getChanges().size(); ++i) {
233             final TransitionInfo.Change change = info.getChanges().get(i);
234 
235             // Only look at changing things. showing/hiding don't need to rotate.
236             if (change.getMode() != TRANSIT_CHANGE) continue;
237 
238             // This container isn't rotating, so we can ignore it.
239             if (change.getEndRotation() == change.getStartRotation()) continue;
240 
241             if (change.getTaskInfo() != null) {
242                 final int anim = change.getRotationAnimation();
243                 if (anim == ROTATION_ANIMATION_UNSPECIFIED
244                         // Fallback animation for seamless should also be default.
245                         || anim == ROTATION_ANIMATION_SEAMLESS) {
246                     return ROTATION_ANIMATION_ROTATE;
247                 }
248                 return anim;
249             }
250         }
251         return ROTATION_ANIMATION_ROTATE;
252     }
253 
254     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)255     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
256             @NonNull SurfaceControl.Transaction startTransaction,
257             @NonNull SurfaceControl.Transaction finishTransaction,
258             @NonNull Transitions.TransitionFinishCallback finishCallback) {
259         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
260                 "start default transition animation, info = %s", info);
261         // If keyguard goes away, we should loadKeyguardExitAnimation. Otherwise this just
262         // immediately finishes since there is no animation for screen-wake.
263         if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
264             startTransaction.apply();
265             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
266             return true;
267         }
268 
269         if (mAnimations.containsKey(transition)) {
270             throw new IllegalStateException("Got a duplicate startAnimation call for "
271                     + transition);
272         }
273         final ArrayList<Animator> animations = new ArrayList<>();
274         mAnimations.put(transition, animations);
275 
276         final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>();
277 
278         final Runnable onAnimFinish = () -> {
279             if (!animations.isEmpty()) return;
280 
281             for (int i = 0; i < counterRotators.size(); ++i) {
282                 counterRotators.valueAt(i).cleanUp(info.getRootLeash());
283             }
284             counterRotators.clear();
285 
286             if (mRotationAnimation != null) {
287                 mRotationAnimation.kill();
288                 mRotationAnimation = null;
289             }
290 
291             mAnimations.remove(transition);
292             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
293         };
294 
295         final int wallpaperTransit = getWallpaperTransitType(info);
296         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
297             final TransitionInfo.Change change = info.getChanges().get(i);
298 
299             if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
300                 int rotateDelta = change.getEndRotation() - change.getStartRotation();
301                 int displayW = change.getEndAbsBounds().width();
302                 int displayH = change.getEndAbsBounds().height();
303                 if (info.getType() == TRANSIT_CHANGE) {
304                     boolean isSeamless = isRotationSeamless(info, mDisplayController);
305                     final int anim = getRotationAnimation(info);
306                     if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
307                         mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
308                                 mTransactionPool, startTransaction, change, info.getRootLeash());
309                         mRotationAnimation.startAnimation(animations, onAnimFinish,
310                                 mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
311                         continue;
312                     }
313                 } else {
314                     // opening/closing an app into a new orientation. Counter-rotate all
315                     // "going-away" things since they are still in the old orientation.
316                     for (int j = info.getChanges().size() - 1; j >= 0; --j) {
317                         final TransitionInfo.Change innerChange = info.getChanges().get(j);
318                         if (!Transitions.isClosingType(innerChange.getMode())
319                                 || !isIndependent(innerChange, info)
320                                 || innerChange.getParent() == null) {
321                             continue;
322                         }
323                         CounterRotator crot = counterRotators.get(innerChange.getParent());
324                         if (crot == null) {
325                             crot = new CounterRotator();
326                             crot.setup(startTransaction,
327                                     info.getChange(innerChange.getParent()).getLeash(),
328                                     rotateDelta, displayW, displayH);
329                             if (crot.getSurface() != null) {
330                                 int layer = info.getChanges().size() - j;
331                                 startTransaction.setLayer(crot.getSurface(), layer);
332                             }
333                             counterRotators.put(innerChange.getParent(), crot);
334                         }
335                         crot.addChild(startTransaction, innerChange.getLeash());
336                     }
337                 }
338             }
339 
340             if (change.getMode() == TRANSIT_CHANGE) {
341                 // No default animation for this, so just update bounds/position.
342                 startTransaction.setPosition(change.getLeash(),
343                         change.getEndAbsBounds().left - change.getEndRelOffset().x,
344                         change.getEndAbsBounds().top - change.getEndRelOffset().y);
345                 if (change.getTaskInfo() != null) {
346                     // Skip non-tasks since those usually have null bounds.
347                     startTransaction.setWindowCrop(change.getLeash(),
348                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
349                 }
350             }
351 
352             // Don't animate anything that isn't independent.
353             if (!TransitionInfo.isIndependent(change, info)) continue;
354 
355             Animation a = loadAnimation(info, change, wallpaperTransit);
356             if (a != null) {
357                 startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
358                         mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */);
359 
360                 if (info.getAnimationOptions() != null) {
361                     attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
362                 }
363             }
364         }
365         startTransaction.apply();
366         TransitionMetrics.getInstance().reportAnimationStart(transition);
367         // run finish now in-case there are no animations
368         onAnimFinish.run();
369         return true;
370     }
371 
372     @Nullable
373     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)374     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
375             @NonNull TransitionRequestInfo request) {
376         return null;
377     }
378 
379     @Override
setAnimScaleSetting(float scale)380     public void setAnimScaleSetting(float scale) {
381         mTransitionAnimationScaleSetting = scale;
382     }
383 
384     @Nullable
loadAnimation(TransitionInfo info, TransitionInfo.Change change, int wallpaperTransit)385     private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
386             int wallpaperTransit) {
387         Animation a = null;
388 
389         final int type = info.getType();
390         final int flags = info.getFlags();
391         final int changeMode = change.getMode();
392         final int changeFlags = change.getFlags();
393         final boolean isOpeningType = Transitions.isOpeningType(type);
394         final boolean enter = Transitions.isOpeningType(changeMode);
395         final boolean isTask = change.getTaskInfo() != null;
396         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
397         final int overrideType = options != null ? options.getType() : ANIM_NONE;
398         final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
399 
400         if (info.isKeyguardGoingAway()) {
401             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
402                     (changeFlags & FLAG_SHOW_WALLPAPER) != 0);
403         } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
404             a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
405         } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
406             if (isOpeningType) {
407                 a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
408             } else {
409                 a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
410             }
411         } else if (changeMode == TRANSIT_CHANGE) {
412             // In the absence of a specific adapter, we just want to keep everything stationary.
413             a = new AlphaAnimation(1.f, 1.f);
414             a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
415         } else if (type == TRANSIT_RELAUNCH) {
416             a = mTransitionAnimation.createRelaunchAnimation(
417                     change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
418         } else if (overrideType == ANIM_CUSTOM
419                 && (canCustomContainer || options.getOverrideTaskTransition())) {
420             a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
421                     ? options.getEnterResId() : options.getExitResId());
422         } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
423             a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
424         } else if (overrideType == ANIM_CLIP_REVEAL) {
425             a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
426                     change.getEndAbsBounds(), change.getEndAbsBounds(),
427                     options.getTransitionBounds());
428         } else if (overrideType == ANIM_SCALE_UP) {
429             a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter,
430                     change.getEndAbsBounds(), options.getTransitionBounds());
431         } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP
432                 || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) {
433             final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP;
434             a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
435                     change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(),
436                     options.getTransitionBounds());
437         } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
438             // This received a transferred starting window, so don't animate
439             return null;
440         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
441             a = mTransitionAnimation.loadDefaultAnimationAttr(enter
442                     ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
443                     : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation);
444         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
445             a = mTransitionAnimation.loadDefaultAnimationAttr(enter
446                     ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
447                     : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation);
448         } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
449             a = mTransitionAnimation.loadDefaultAnimationAttr(enter
450                     ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
451                     : R.styleable.WindowAnimation_wallpaperOpenExitAnimation);
452         } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
453             a = mTransitionAnimation.loadDefaultAnimationAttr(enter
454                     ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
455                     : R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
456         } else if (type == TRANSIT_OPEN) {
457             if (isTask) {
458                 a = mTransitionAnimation.loadDefaultAnimationAttr(enter
459                         ? R.styleable.WindowAnimation_taskOpenEnterAnimation
460                         : R.styleable.WindowAnimation_taskOpenExitAnimation);
461             } else {
462                 if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
463                     a = mTransitionAnimation.loadDefaultAnimationRes(
464                             R.anim.activity_translucent_open_enter);
465                 } else {
466                     a = mTransitionAnimation.loadDefaultAnimationAttr(enter
467                             ? R.styleable.WindowAnimation_activityOpenEnterAnimation
468                             : R.styleable.WindowAnimation_activityOpenExitAnimation);
469                 }
470             }
471         } else if (type == TRANSIT_TO_FRONT) {
472             a = mTransitionAnimation.loadDefaultAnimationAttr(enter
473                     ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
474                     : R.styleable.WindowAnimation_taskToFrontExitAnimation);
475         } else if (type == TRANSIT_CLOSE) {
476             if (isTask) {
477                 a = mTransitionAnimation.loadDefaultAnimationAttr(enter
478                         ? R.styleable.WindowAnimation_taskCloseEnterAnimation
479                         : R.styleable.WindowAnimation_taskCloseExitAnimation);
480             } else {
481                 if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
482                     a = mTransitionAnimation.loadDefaultAnimationRes(
483                             R.anim.activity_translucent_close_exit);
484                 } else {
485                     a = mTransitionAnimation.loadDefaultAnimationAttr(enter
486                             ? R.styleable.WindowAnimation_activityCloseEnterAnimation
487                             : R.styleable.WindowAnimation_activityCloseExitAnimation);
488                 }
489             }
490         } else if (type == TRANSIT_TO_BACK) {
491             a = mTransitionAnimation.loadDefaultAnimationAttr(enter
492                     ? R.styleable.WindowAnimation_taskToBackEnterAnimation
493                     : R.styleable.WindowAnimation_taskToBackExitAnimation);
494         }
495 
496         if (a != null) {
497             if (!a.isInitialized()) {
498                 Rect end = change.getEndAbsBounds();
499                 a.initialize(end.width(), end.height(), end.width(), end.height());
500             }
501             a.restrictDuration(MAX_ANIMATION_DURATION);
502             a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
503         }
504         return a;
505     }
506 
startSurfaceAnimation(@onNull ArrayList<Animator> animations, @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor, @Nullable Point position)507     static void startSurfaceAnimation(@NonNull ArrayList<Animator> animations,
508             @NonNull Animation anim, @NonNull SurfaceControl leash,
509             @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
510             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor,
511             @Nullable Point position) {
512         final SurfaceControl.Transaction transaction = pool.acquire();
513         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
514         final Transformation transformation = new Transformation();
515         final float[] matrix = new float[9];
516         // Animation length is already expected to be scaled.
517         va.overrideDurationScale(1.0f);
518         va.setDuration(anim.computeDurationHint());
519         va.addUpdateListener(animation -> {
520             final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
521 
522             applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
523                     position);
524         });
525 
526         final Runnable finisher = () -> {
527             applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
528                     position);
529 
530             pool.release(transaction);
531             mainExecutor.execute(() -> {
532                 animations.remove(va);
533                 finishCallback.run();
534             });
535         };
536         va.addListener(new AnimatorListenerAdapter() {
537             @Override
538             public void onAnimationEnd(Animator animation) {
539                 finisher.run();
540             }
541 
542             @Override
543             public void onAnimationCancel(Animator animation) {
544                 finisher.run();
545             }
546         });
547         animations.add(va);
548         animExecutor.execute(va::start);
549     }
550 
attachThumbnail(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options)551     private void attachThumbnail(@NonNull ArrayList<Animator> animations,
552             @NonNull Runnable finishCallback, TransitionInfo.Change change,
553             TransitionInfo.AnimationOptions options) {
554         final boolean isTask = change.getTaskInfo() != null;
555         final boolean isOpen = Transitions.isOpeningType(change.getMode());
556         final boolean isClose = Transitions.isClosingType(change.getMode());
557         if (isOpen) {
558             if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
559                 attachCrossProfileThunmbnailAnimation(animations, finishCallback, change);
560             } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
561                 attachThumbnailAnimation(animations, finishCallback, change, options);
562             }
563         } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
564             attachThumbnailAnimation(animations, finishCallback, change, options);
565         }
566     }
567 
attachCrossProfileThunmbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change)568     private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
569             @NonNull Runnable finishCallback, TransitionInfo.Change change) {
570         final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
571                 ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
572         final Rect bounds = change.getEndAbsBounds();
573         final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
574                 thumbnailDrawableRes, bounds);
575         if (thumbnail == null) {
576             return;
577         }
578 
579         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
580         final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
581                 change.getLeash(), thumbnail, transaction);
582         final Animation a =
583                 mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds);
584         if (a == null) {
585             return;
586         }
587 
588         final Runnable finisher = () -> {
589             wt.destroy(transaction);
590             mTransactionPool.release(transaction);
591 
592             finishCallback.run();
593         };
594         a.restrictDuration(MAX_ANIMATION_DURATION);
595         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
596         startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
597                 mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top));
598     }
599 
attachThumbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options)600     private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
601             @NonNull Runnable finishCallback, TransitionInfo.Change change,
602             TransitionInfo.AnimationOptions options) {
603         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
604         final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
605                 change.getLeash(), options.getThumbnail(), transaction);
606         final Rect bounds = change.getEndAbsBounds();
607         final int orientation = mContext.getResources().getConfiguration().orientation;
608         final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds,
609                 mInsets, options.getThumbnail(), orientation, null /* startRect */,
610                 options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP);
611 
612         final Runnable finisher = () -> {
613             wt.destroy(transaction);
614             mTransactionPool.release(transaction);
615 
616             finishCallback.run();
617         };
618         a.restrictDuration(MAX_ANIMATION_DURATION);
619         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
620         startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
621                 mMainExecutor, mAnimExecutor, null /* position */);
622     }
623 
getWallpaperTransitType(TransitionInfo info)624     private static int getWallpaperTransitType(TransitionInfo info) {
625         boolean hasOpenWallpaper = false;
626         boolean hasCloseWallpaper = false;
627 
628         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
629             final TransitionInfo.Change change = info.getChanges().get(i);
630             if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
631                 if (Transitions.isOpeningType(change.getMode())) {
632                     hasOpenWallpaper = true;
633                 } else if (Transitions.isClosingType(change.getMode())) {
634                     hasCloseWallpaper = true;
635                 }
636             }
637         }
638 
639         if (hasOpenWallpaper && hasCloseWallpaper) {
640             return Transitions.isOpeningType(info.getType())
641                     ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
642         } else if (hasOpenWallpaper) {
643             return WALLPAPER_TRANSITION_OPEN;
644         } else if (hasCloseWallpaper) {
645             return WALLPAPER_TRANSITION_CLOSE;
646         } else {
647             return WALLPAPER_TRANSITION_NONE;
648         }
649     }
650 
applyTransformation(long time, SurfaceControl.Transaction t, SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix, Point position)651     private static void applyTransformation(long time, SurfaceControl.Transaction t,
652             SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
653             Point position) {
654         anim.getTransformation(time, transformation);
655         if (position != null) {
656             transformation.getMatrix().postTranslate(position.x, position.y);
657         }
658         t.setMatrix(leash, transformation.getMatrix(), matrix);
659         t.setAlpha(leash, transformation.getAlpha());
660         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
661         t.apply();
662     }
663 }
664