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