1 /*
2  * Copyright (C) 2022 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_FROM_STYLE;
20 import static android.app.ActivityOptions.ANIM_NONE;
21 import static android.view.WindowManager.TRANSIT_CLOSE;
22 import static android.view.WindowManager.TRANSIT_OPEN;
23 import static android.view.WindowManager.TRANSIT_TO_BACK;
24 import static android.view.WindowManager.TRANSIT_TO_FRONT;
25 import static android.view.WindowManager.transitTypeToString;
26 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
27 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
28 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
29 
30 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
31 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
32 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
33 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
34 
35 import android.annotation.ColorInt;
36 import android.annotation.NonNull;
37 import android.annotation.Nullable;
38 import android.graphics.BitmapShader;
39 import android.graphics.Canvas;
40 import android.graphics.Color;
41 import android.graphics.Insets;
42 import android.graphics.Paint;
43 import android.graphics.PixelFormat;
44 import android.graphics.Rect;
45 import android.graphics.Shader;
46 import android.view.Surface;
47 import android.view.SurfaceControl;
48 import android.view.animation.Animation;
49 import android.view.animation.Transformation;
50 import android.window.ScreenCapture;
51 import android.window.TransitionInfo;
52 
53 import com.android.internal.R;
54 import com.android.internal.policy.TransitionAnimation;
55 import com.android.internal.protolog.common.ProtoLog;
56 import com.android.wm.shell.protolog.ShellProtoLogGroup;
57 import com.android.wm.shell.util.TransitionUtil;
58 
59 /** The helper class that provides methods for adding styles to transition animations. */
60 public class TransitionAnimationHelper {
61 
62     /** Loads the animation that is defined through attribute id for the given transition. */
63     @Nullable
loadAttributeAnimation(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition)64     public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
65             @NonNull TransitionInfo.Change change, int wallpaperTransit,
66             @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
67         final int type = info.getType();
68         final int changeMode = change.getMode();
69         final int changeFlags = change.getFlags();
70         final boolean enter = TransitionUtil.isOpeningType(changeMode);
71         final boolean isTask = change.getTaskInfo() != null;
72         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
73         final int overrideType = options != null ? options.getType() : ANIM_NONE;
74         int animAttr = 0;
75         boolean translucent = false;
76         if (isDreamTransition) {
77             if (type == TRANSIT_OPEN) {
78                 animAttr = enter
79                         ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
80                         : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
81             } else if (type == TRANSIT_CLOSE) {
82                 animAttr = enter
83                         ? 0
84                         : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
85             }
86         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
87             animAttr = enter
88                     ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
89                     : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
90         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
91             animAttr = enter
92                     ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
93                     : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
94         } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
95             animAttr = enter
96                     ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
97                     : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
98         } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
99             animAttr = enter
100                     ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
101                     : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
102         } else if (type == TRANSIT_OPEN) {
103             // We will translucent open animation for translucent activities and tasks. Choose
104             // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
105             // TransitionAnimation loads appropriate animation later.
106             translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
107             if (isTask && translucent && !enter) {
108                 // For closing translucent tasks, use the activity close animation
109                 animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
110             } else if (isTask && !translucent) {
111                 animAttr = enter
112                         ? R.styleable.WindowAnimation_taskOpenEnterAnimation
113                         : R.styleable.WindowAnimation_taskOpenExitAnimation;
114             } else {
115                 animAttr = enter
116                         ? R.styleable.WindowAnimation_activityOpenEnterAnimation
117                         : R.styleable.WindowAnimation_activityOpenExitAnimation;
118             }
119         } else if (type == TRANSIT_TO_FRONT) {
120             animAttr = enter
121                     ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
122                     : R.styleable.WindowAnimation_taskToFrontExitAnimation;
123         } else if (type == TRANSIT_CLOSE) {
124             if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
125                 translucent = true;
126             }
127             if (isTask && !translucent) {
128                 animAttr = enter
129                         ? R.styleable.WindowAnimation_taskCloseEnterAnimation
130                         : R.styleable.WindowAnimation_taskCloseExitAnimation;
131             } else {
132                 animAttr = enter
133                         ? R.styleable.WindowAnimation_activityCloseEnterAnimation
134                         : R.styleable.WindowAnimation_activityCloseExitAnimation;
135             }
136         } else if (type == TRANSIT_TO_BACK) {
137             animAttr = enter
138                     ? R.styleable.WindowAnimation_taskToBackEnterAnimation
139                     : R.styleable.WindowAnimation_taskToBackExitAnimation;
140         }
141 
142         Animation a = null;
143         if (animAttr != 0) {
144             if (overrideType == ANIM_FROM_STYLE && !isTask) {
145                 final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition =
146                         getCustomActivityTransition(animAttr, options);
147                 if (customTransition != null) {
148                     a = loadCustomActivityTransition(
149                             customTransition, options, enter, transitionAnimation);
150                 } else {
151                     a = transitionAnimation
152                             .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
153                                     animAttr, translucent);
154                 }
155             } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) {
156                 // Un-styled translucent activities technically have undefined animations; however,
157                 // as is always the case, some apps now rely on this being no-animation, so skip
158                 // loading animations here.
159             } else {
160                 a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
161             }
162         }
163 
164         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
165                 "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
166                 transitTypeToString(type),
167                 enter);
168         return a;
169     }
170 
getCustomActivityTransition( int animAttr, TransitionInfo.AnimationOptions options)171     static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition(
172             int animAttr, TransitionInfo.AnimationOptions options) {
173         boolean isOpen = false;
174         switch (animAttr) {
175             case R.styleable.WindowAnimation_activityOpenEnterAnimation:
176             case R.styleable.WindowAnimation_activityOpenExitAnimation:
177                 isOpen = true;
178                 break;
179             case R.styleable.WindowAnimation_activityCloseEnterAnimation:
180             case R.styleable.WindowAnimation_activityCloseExitAnimation:
181                 break;
182             default:
183                 return null;
184         }
185 
186         return options.getCustomActivityTransition(isOpen);
187     }
188 
loadCustomActivityTransition( @onNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, TransitionInfo.AnimationOptions options, boolean enter, TransitionAnimation transitionAnimation)189     static Animation loadCustomActivityTransition(
190             @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
191             TransitionInfo.AnimationOptions options, boolean enter,
192             TransitionAnimation transitionAnimation) {
193         final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
194                 enter ? transitionAnim.getCustomEnterResId()
195                         : transitionAnim.getCustomExitResId());
196         if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
197             a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
198         }
199         return a;
200     }
201 
202     /**
203      * Gets the background {@link ColorInt} for the given transition animation if it is set.
204      *
205      * @param defaultColor  {@link ColorInt} to return if there is no background color specified by
206      *                      the given transition animation.
207      */
208     @ColorInt
getTransitionBackgroundColorIfSet(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Animation a, @ColorInt int defaultColor)209     public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info,
210             @NonNull TransitionInfo.Change change, @NonNull Animation a,
211             @ColorInt int defaultColor) {
212         if (!a.getShowBackdrop()) {
213             return defaultColor;
214         }
215         if (info.getAnimationOptions() != null
216                 && info.getAnimationOptions().getBackgroundColor() != 0) {
217             // If available use the background color provided through AnimationOptions
218             return info.getAnimationOptions().getBackgroundColor();
219         } else if (a.getBackdropColor() != 0) {
220             // Otherwise fallback on the background color provided through the animation
221             // definition.
222             return a.getBackdropColor();
223         } else if (change.getBackgroundColor() != 0) {
224             // Otherwise default to the window's background color if provided through
225             // the theme as the background color for the animation - the top most window
226             // with a valid background color and showBackground set takes precedence.
227             return change.getBackgroundColor();
228         }
229         return defaultColor;
230     }
231 
232     /**
233      * Adds the given {@code backgroundColor} as the background color to the transition animation.
234      */
addBackgroundToTransition(@onNull SurfaceControl rootLeash, @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)235     public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash,
236             @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction,
237             @NonNull SurfaceControl.Transaction finishTransaction) {
238         if (backgroundColor == 0) {
239             // No background color.
240             return;
241         }
242         final Color bgColor = Color.valueOf(backgroundColor);
243         final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
244         final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
245                 .setName("Animation Background")
246                 .setParent(rootLeash)
247                 .setColorLayer()
248                 .setOpaque(true)
249                 .build();
250         startTransaction
251                 .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
252                 .setColor(animationBackgroundSurface, colorArray)
253                 .show(animationBackgroundSurface);
254         finishTransaction.remove(animationBackgroundSurface);
255     }
256 
257     /**
258      * Adds edge extension surface to the given {@code change} for edge extension animation.
259      */
edgeExtendWindow(@onNull TransitionInfo.Change change, @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)260     public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
261             @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
262             @NonNull SurfaceControl.Transaction finishTransaction) {
263         // Do not create edge extension surface for transfer starting window change.
264         // The app surface could be empty thus nothing can draw on the hardware renderer, which will
265         // block this thread when calling Surface#unlockCanvasAndPost.
266         if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
267             return;
268         }
269         final Transformation transformationAtStart = new Transformation();
270         a.getTransformationAt(0, transformationAtStart);
271         final Transformation transformationAtEnd = new Transformation();
272         a.getTransformationAt(1, transformationAtEnd);
273 
274         // We want to create an extension surface that is the maximal size and the animation will
275         // take care of cropping any part that overflows.
276         final Insets maxExtensionInsets = Insets.min(
277                 transformationAtStart.getInsets(), transformationAtEnd.getInsets());
278 
279         final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
280                 change.getEndAbsBounds().height());
281         final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
282                 change.getEndAbsBounds().width());
283         if (maxExtensionInsets.left < 0) {
284             final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
285             final Rect extensionRect = new Rect(0, 0,
286                     -maxExtensionInsets.left, targetSurfaceHeight);
287             final int xPos = maxExtensionInsets.left;
288             final int yPos = 0;
289             createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
290                     "Left Edge Extension", startTransaction, finishTransaction);
291         }
292 
293         if (maxExtensionInsets.top < 0) {
294             final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
295             final Rect extensionRect = new Rect(0, 0,
296                     targetSurfaceWidth, -maxExtensionInsets.top);
297             final int xPos = 0;
298             final int yPos = maxExtensionInsets.top;
299             createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
300                     "Top Edge Extension", startTransaction, finishTransaction);
301         }
302 
303         if (maxExtensionInsets.right < 0) {
304             final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
305                     targetSurfaceWidth, targetSurfaceHeight);
306             final Rect extensionRect = new Rect(0, 0,
307                     -maxExtensionInsets.right, targetSurfaceHeight);
308             final int xPos = targetSurfaceWidth;
309             final int yPos = 0;
310             createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
311                     "Right Edge Extension", startTransaction, finishTransaction);
312         }
313 
314         if (maxExtensionInsets.bottom < 0) {
315             final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
316                     targetSurfaceWidth, targetSurfaceHeight);
317             final Rect extensionRect = new Rect(0, 0,
318                     targetSurfaceWidth, -maxExtensionInsets.bottom);
319             final int xPos = maxExtensionInsets.left;
320             final int yPos = targetSurfaceHeight;
321             createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
322                     "Bottom Edge Extension", startTransaction, finishTransaction);
323         }
324     }
325 
326     /**
327      * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
328      * animation.
329      */
createExtensionSurface(@onNull SurfaceControl surfaceToExtend, @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos, @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)330     private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
331             @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
332             @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
333             @NonNull SurfaceControl.Transaction finishTransaction) {
334         final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
335                 .setName(layerName)
336                 .setParent(surfaceToExtend)
337                 .setHidden(true)
338                 .setCallsite("TransitionAnimationHelper#createExtensionSurface")
339                 .setOpaque(true)
340                 .setBufferSize(extensionRect.width(), extensionRect.height())
341                 .build();
342 
343         final ScreenCapture.LayerCaptureArgs captureArgs =
344                 new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
345                         .setSourceCrop(edgeBounds)
346                         .setFrameScale(1)
347                         .setPixelFormat(PixelFormat.RGBA_8888)
348                         .setChildrenOnly(true)
349                         .setAllowProtected(true)
350                         .setCaptureSecureLayers(true)
351                         .build();
352         final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
353                 ScreenCapture.captureLayers(captureArgs);
354 
355         if (edgeBuffer == null) {
356             ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
357                     "Failed to capture edge of window.");
358             return null;
359         }
360 
361         final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
362                 Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
363         final Paint paint = new Paint();
364         paint.setShader(shader);
365 
366         final Surface surface = new Surface(edgeExtensionLayer);
367         final Canvas c = surface.lockHardwareCanvas();
368         c.drawRect(extensionRect, paint);
369         surface.unlockCanvasAndPost(c);
370         surface.release();
371 
372         startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
373         startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
374         startTransaction.setVisibility(edgeExtensionLayer, true);
375         finishTransaction.remove(edgeExtensionLayer);
376 
377         return edgeExtensionLayer;
378     }
379 }
380