• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_CLOSE_PREPARE_BACK_NAVIGATION;
23 import static android.view.WindowManager.TRANSIT_OPEN;
24 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
25 import static android.view.WindowManager.TRANSIT_TO_BACK;
26 import static android.view.WindowManager.TRANSIT_TO_FRONT;
27 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
28 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
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 import static com.android.wm.shell.transition.Transitions.transitTypeToString;
36 
37 import android.annotation.ColorInt;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.app.WindowConfiguration;
41 import android.graphics.Color;
42 import android.graphics.Insets;
43 import android.graphics.Rect;
44 import android.util.SparseArray;
45 import android.view.InsetsSource;
46 import android.view.InsetsState;
47 import android.view.SurfaceControl;
48 import android.view.WindowManager;
49 import android.view.animation.Animation;
50 import android.window.TransitionInfo;
51 
52 import com.android.internal.R;
53 import com.android.internal.policy.TransitionAnimation;
54 import com.android.internal.protolog.ProtoLog;
55 import com.android.wm.shell.common.DisplayController;
56 import com.android.wm.shell.common.DisplayInsetsController;
57 import com.android.wm.shell.common.DisplayLayout;
58 import com.android.wm.shell.protolog.ShellProtoLogGroup;
59 import com.android.wm.shell.shared.TransitionUtil;
60 
61 /** The helper class that provides methods for adding styles to transition animations. */
62 public class TransitionAnimationHelper {
63 
64     /** Loads the animation that is defined through attribute id for the given transition. */
65     @Nullable
loadAttributeAnimation(@indowManager.TransitionType int type, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition)66     public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
67             @NonNull TransitionInfo info,
68             @NonNull TransitionInfo.Change change, int wallpaperTransit,
69             @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
70         final int changeMode = change.getMode();
71         final int changeFlags = change.getFlags();
72         final boolean enter = TransitionUtil.isOpeningType(changeMode);
73         final boolean isTask = change.getTaskInfo() != null;
74         final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
75         final boolean isCoveredByOpaqueFullscreenChange =
76                 isCoveredByOpaqueFullscreenChange(info, change);
77         final TransitionInfo.AnimationOptions options = change.getAnimationOptions();
78         final int overrideType = options != null ? options.getType() : ANIM_NONE;
79         int animAttr = 0;
80         boolean translucent = false;
81         if (isDreamTransition) {
82             if (type == TRANSIT_OPEN) {
83                 animAttr = enter
84                         ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
85                         : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
86             } else if (type == TRANSIT_CLOSE) {
87                 animAttr = enter
88                         ? 0
89                         : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
90             }
91         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
92             animAttr = enter
93                     ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
94                     : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
95         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
96             animAttr = enter
97                     ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
98                     : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
99         } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
100             animAttr = enter
101                     ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
102                     : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
103         } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
104             animAttr = enter
105                     ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
106                     : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
107         } else if (!isCoveredByOpaqueFullscreenChange
108                 && isFreeform
109                 && TransitionUtil.isOpeningMode(type)
110                 && change.getMode() == TRANSIT_TO_BACK) {
111             // Set translucent here so TransitionAnimation loads the appropriate animations for
112             // translucent activities and tasks later
113             translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
114             // The main Task is launching or being brought to front, this Task is being minimized
115             animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
116         } else if (!isCoveredByOpaqueFullscreenChange
117                 && isFreeform
118                 && type == TRANSIT_TO_FRONT
119                 && change.getMode() == TRANSIT_TO_FRONT) {
120             // Set translucent here so TransitionAnimation loads the appropriate animations for
121             // translucent activities and tasks later
122             translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
123             // Bring the minimized Task back to front
124             animAttr = R.styleable.WindowAnimation_activityOpenEnterAnimation;
125         } else if (type == TRANSIT_OPEN) {
126             // We will translucent open animation for translucent activities and tasks. Choose
127             // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
128             // TransitionAnimation loads appropriate animation later.
129             translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
130             if (isTask && translucent && !enter) {
131                 // For closing translucent tasks, use the activity close animation
132                 animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
133             } else if (isTask && !translucent) {
134                 animAttr = enter
135                         ? R.styleable.WindowAnimation_taskOpenEnterAnimation
136                         : R.styleable.WindowAnimation_taskOpenExitAnimation;
137             } else {
138                 animAttr = enter
139                         ? R.styleable.WindowAnimation_activityOpenEnterAnimation
140                         : R.styleable.WindowAnimation_activityOpenExitAnimation;
141             }
142         } else if (type == TRANSIT_TO_FRONT) {
143             animAttr = enter
144                     ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
145                     : R.styleable.WindowAnimation_taskToFrontExitAnimation;
146         } else if (type == TRANSIT_CLOSE) {
147             if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
148                 translucent = true;
149             }
150             if (isTask && !translucent) {
151                 animAttr = enter
152                         ? R.styleable.WindowAnimation_taskCloseEnterAnimation
153                         : R.styleable.WindowAnimation_taskCloseExitAnimation;
154             } else {
155                 animAttr = enter
156                         ? R.styleable.WindowAnimation_activityCloseEnterAnimation
157                         : R.styleable.WindowAnimation_activityCloseExitAnimation;
158             }
159         } else if (type == TRANSIT_TO_BACK) {
160             animAttr = enter
161                     ? R.styleable.WindowAnimation_taskToBackEnterAnimation
162                     : R.styleable.WindowAnimation_taskToBackExitAnimation;
163         }
164 
165         Animation a = null;
166         if (animAttr != 0) {
167             if (overrideType == ANIM_FROM_STYLE && !isTask) {
168                 final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition =
169                         getCustomActivityTransition(animAttr, options);
170                 if (customTransition != null) {
171                     a = loadCustomActivityTransition(
172                             customTransition, options, enter, transitionAnimation);
173                 } else {
174                     a = transitionAnimation
175                             .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
176                                     animAttr, translucent);
177                 }
178             } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) {
179                 // Un-styled translucent activities technically have undefined animations; however,
180                 // as is always the case, some apps now rely on this being no-animation, so skip
181                 // loading animations here.
182             } else {
183                 a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
184             }
185         }
186 
187         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
188                 "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
189                 transitTypeToString(type),
190                 enter);
191         return a;
192     }
193 
getCustomActivityTransition( int animAttr, TransitionInfo.AnimationOptions options)194     static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition(
195             int animAttr, TransitionInfo.AnimationOptions options) {
196         boolean isOpen = false;
197         switch (animAttr) {
198             case R.styleable.WindowAnimation_activityOpenEnterAnimation:
199             case R.styleable.WindowAnimation_activityOpenExitAnimation:
200                 isOpen = true;
201                 break;
202             case R.styleable.WindowAnimation_activityCloseEnterAnimation:
203             case R.styleable.WindowAnimation_activityCloseExitAnimation:
204                 break;
205             default:
206                 return null;
207         }
208 
209         return options.getCustomActivityTransition(isOpen);
210     }
211 
212     /**
213      * Gets the final transition type from {@link TransitionInfo} for determining the animation.
214      */
getTransitionTypeFromInfo(@onNull TransitionInfo info)215     public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) {
216         final int type = info.getType();
217         // This back navigation is canceled, check whether the transition should be open or close
218         if (type == TRANSIT_PREPARE_BACK_NAVIGATION
219                 || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
220             if (!info.getChanges().isEmpty()) {
221                 final TransitionInfo.Change change = info.getChanges().get(0);
222                 return TransitionUtil.isOpeningMode(change.getMode())
223                         ? TRANSIT_OPEN : TRANSIT_CLOSE;
224             }
225         }
226         // If the info transition type is opening transition, iterate its changes to see if it
227         // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation.
228         if (type == TRANSIT_OPEN) {
229             boolean hasOpenTransit = false;
230             for (TransitionInfo.Change change : info.getChanges()) {
231                 if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
232                         && !TransitionUtil.isOrderOnly(change)) {
233                     // This isn't an activity-level transition.
234                     return type;
235                 }
236                 if (change.getTaskInfo() != null
237                         && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
238                     // Ignore non-activity containers.
239                     continue;
240                 }
241                 if (change.getMode() == TRANSIT_OPEN) {
242                     hasOpenTransit = true;
243                     break;
244                 }
245             }
246             if (!hasOpenTransit) {
247                 return TRANSIT_CLOSE;
248             }
249         }
250         return type;
251     }
252 
loadCustomActivityTransition( @onNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, TransitionInfo.AnimationOptions options, boolean enter, TransitionAnimation transitionAnimation)253     static Animation loadCustomActivityTransition(
254             @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
255             TransitionInfo.AnimationOptions options, boolean enter,
256             TransitionAnimation transitionAnimation) {
257         final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
258                 enter ? transitionAnim.getCustomEnterResId()
259                         : transitionAnim.getCustomExitResId());
260         if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
261             a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
262         }
263         return a;
264     }
265 
266     /**
267      * Gets the background {@link ColorInt} for the given transition animation if it is set.
268      *
269      * @param defaultColor  {@link ColorInt} to return if there is no background color specified by
270      *                      the given transition animation.
271      */
272     @ColorInt
getTransitionBackgroundColorIfSet(@onNull TransitionInfo.Change change, @NonNull Animation a, @ColorInt int defaultColor)273     public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo.Change change,
274             @NonNull Animation a, @ColorInt int defaultColor) {
275         if (!a.getShowBackdrop()) {
276             return defaultColor;
277         } else if (a.getBackdropColor() != 0) {
278             // Otherwise fallback on the background color provided through the animation
279             // definition.
280             return a.getBackdropColor();
281         } else if (change.getBackgroundColor() != 0) {
282             // Otherwise default to the window's background color if provided through
283             // the theme as the background color for the animation - the top most window
284             // with a valid background color and showBackground set takes precedence.
285             return change.getBackgroundColor();
286         }
287         return defaultColor;
288     }
289 
290     /**
291      * Adds the given {@code backgroundColor} as the background color to the transition animation.
292      */
addBackgroundToTransition(@onNull SurfaceControl rootLeash, @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)293     public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash,
294             @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction,
295             @NonNull SurfaceControl.Transaction finishTransaction) {
296         if (backgroundColor == 0) {
297             // No background color.
298             return;
299         }
300         final Color bgColor = Color.valueOf(backgroundColor);
301         final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
302         final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
303                 .setName("Animation Background")
304                 .setParent(rootLeash)
305                 .setColorLayer()
306                 .setOpaque(true)
307                 .setCallsite("TransitionAnimationHelper.addBackgroundToTransition")
308                 .build();
309         startTransaction
310                 .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
311                 .setColor(animationBackgroundSurface, colorArray)
312                 .show(animationBackgroundSurface);
313         finishTransaction.remove(animationBackgroundSurface);
314     }
315 
316     /**
317      * Returns whether there is an opaque fullscreen Change positioned in front of the given Change
318      * in the given TransitionInfo.
319      */
isCoveredByOpaqueFullscreenChange( TransitionInfo info, TransitionInfo.Change change)320     static boolean isCoveredByOpaqueFullscreenChange(
321             TransitionInfo info, TransitionInfo.Change change) {
322         // TransitionInfo#getChanges() are ordered from front to back
323         for (TransitionInfo.Change coveringChange : info.getChanges()) {
324             if (coveringChange == change) {
325                 return false;
326             }
327             if ((coveringChange.getFlags() & FLAG_TRANSLUCENT) == 0
328                     && coveringChange.getTaskInfo() != null
329                     && coveringChange.getTaskInfo().getWindowingMode()
330                     == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
331                 return true;
332             }
333         }
334         return false;
335     }
336 
337     /**
338      * In some situations (eg. TaskBar) the content area of a display appears to be rounded. For
339      * these situations, we may want the animation to also express the same rounded corners (even
340      * though in steady-state, the app internally manages the insets). This class Keeps track of,
341      * and provides, the bounds of rounded-corner display content.
342      *
343      * This is used to enable already-running animations to adapt to changes in taskbar/navbar
344      * position live.
345      */
346     public static class RoundedContentPerDisplay implements
347             DisplayInsetsController.OnInsetsChangedListener {
348 
349         /** The current bounds of the display content (post-inset). */
350         final Rect mBounds = new Rect();
351 
352         @Override
insetsChanged(InsetsState insetsState)353         public void insetsChanged(InsetsState insetsState) {
354             Insets insets = Insets.NONE;
355             for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
356                 final InsetsSource source = insetsState.sourceAt(i);
357                 if (!source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
358                     continue;
359                 }
360                 insets = Insets.max(source.calculateInsets(insetsState.getDisplayFrame(), false),
361                         insets);
362             }
363             mBounds.set(insetsState.getDisplayFrame());
364             mBounds.inset(insets);
365         }
366     }
367 
368     /**
369      * Keeps track of the bounds of rounded-corner display content (post-inset).
370      *
371      * @see RoundedContentPerDisplay
372      */
373     public static class RoundedContentTracker implements
374             DisplayController.OnDisplaysChangedListener {
375         final DisplayController mDisplayController;
376         final DisplayInsetsController mDisplayInsetsController;
377         final SparseArray<RoundedContentPerDisplay> mPerDisplay = new SparseArray<>();
378 
RoundedContentTracker(DisplayController dc, DisplayInsetsController dic)379         RoundedContentTracker(DisplayController dc, DisplayInsetsController dic) {
380             mDisplayController = dc;
381             mDisplayInsetsController = dic;
382         }
383 
init()384         void init() {
385             mDisplayController.addDisplayWindowListener(this);
386         }
387 
forDisplay(int displayId)388         RoundedContentPerDisplay forDisplay(int displayId) {
389             return mPerDisplay.get(displayId);
390         }
391 
392         @Override
onDisplayAdded(int displayId)393         public void onDisplayAdded(int displayId) {
394             final RoundedContentPerDisplay perDisplay = new RoundedContentPerDisplay();
395             mDisplayInsetsController.addInsetsChangedListener(displayId, perDisplay);
396             mPerDisplay.put(displayId, perDisplay);
397             final DisplayLayout dl = mDisplayController.getDisplayLayout(displayId);
398             perDisplay.mBounds.set(0, 0, dl.width(), dl.height());
399         }
400 
401         @Override
onDisplayRemoved(int displayId)402         public void onDisplayRemoved(int displayId) {
403             final RoundedContentPerDisplay listener = mPerDisplay.removeReturnOld(displayId);
404             if (listener != null) {
405                 mDisplayInsetsController.removeInsetsChangedListener(displayId, listener);
406             }
407         }
408     }
409 }
410