Implement ActivityEmbedding Shell transition animation with showBackdrop

Refactor background color handling from DefaultTransitionHandler to a
helper class, so that we can reuse it for ActivityEmbedding as well.

Bug: 207070762
Test: manual
Change-Id: I6034ddd1928184a969848f0be496247032763b40
This commit is contained in:
Chris Li
2022-09-19 17:28:31 +08:00
parent d316c7d45c
commit 1c939d4f0f
4 changed files with 306 additions and 182 deletions

View File

@@ -20,6 +20,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -42,7 +45,6 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
/** To run the ActivityEmbedding animations. */
class ActivityEmbeddingAnimationRunner {
@@ -85,7 +87,7 @@ class ActivityEmbeddingAnimationRunner {
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Runnable animationFinishCallback) {
final List<ActivityEmbeddingAnimationAdapter> adapters =
createAnimationAdapters(info, startTransaction);
createAnimationAdapters(info, startTransaction, finishTransaction);
long duration = 0;
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -129,7 +131,8 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -145,23 +148,25 @@ class ActivityEmbeddingAnimationRunner {
return createChangeAnimationAdapters(info, startTransaction);
}
if (Transitions.isClosingType(info.getType())) {
return createCloseAnimationAdapters(info);
return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
}
return createOpenAnimationAdapters(info);
return createOpenAnimationAdapters(info, startTransaction, finishTransaction);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
@NonNull TransitionInfo info) {
return createOpenCloseAnimationAdapters(info, true /* isOpening */,
mAnimationSpec::loadOpenAnimation);
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
true /* isOpening */, mAnimationSpec::loadOpenAnimation);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
@NonNull TransitionInfo info) {
return createOpenCloseAnimationAdapters(info, false /* isOpening */,
mAnimationSpec::loadCloseAnimation);
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
false /* isOpening */, mAnimationSpec::loadCloseAnimation);
}
/**
@@ -170,8 +175,9 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
@NonNull TransitionInfo info, boolean isOpening,
@NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) {
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
@NonNull AnimationProvider animationProvider) {
// We need to know if the change window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one.
final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
@@ -194,7 +200,8 @@ class ActivityEmbeddingAnimationRunner {
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
for (TransitionInfo.Change change : openingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
change, animationProvider, openingWholeScreenBounds);
info, change, startTransaction, finishTransaction, animationProvider,
openingWholeScreenBounds);
if (isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -202,7 +209,8 @@ class ActivityEmbeddingAnimationRunner {
}
for (TransitionInfo.Change change : closingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
change, animationProvider, closingWholeScreenBounds);
info, change, startTransaction, finishTransaction, animationProvider,
closingWholeScreenBounds);
if (!isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -213,10 +221,18 @@ class ActivityEmbeddingAnimationRunner {
@NonNull
private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull TransitionInfo.Change change,
@NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
@NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
// We may want to show a background color for open/close transition.
final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
0 /* defaultColor */);
if (backgroundColor != 0) {
addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
finishTransaction);
}
return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
wholeAnimationBounds);
}
@@ -322,4 +338,10 @@ class ActivityEmbeddingAnimationRunner {
return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(),
animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
}
/** To provide an {@link Animation} based on the transition infos. */
private interface AnimationProvider {
Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
@NonNull Rect animationBounds);
}
}

View File

@@ -17,6 +17,9 @@
package com.android.wm.shell.activityembedding;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -33,7 +36,6 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.wm.shell.transition.Transitions;
@@ -175,16 +177,20 @@ class ActivityEmbeddingAnimationSpec {
}
@NonNull
Animation loadOpenAnimation(@NonNull TransitionInfo.Change change,
@NonNull Rect wholeAnimationBounds) {
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
// TODO(b/207070762):
// 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
// 2. Implement edgeExtension version
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? R.anim.task_fragment_open_enter
: R.anim.task_fragment_open_exit);
// TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_open_enter
: com.android.internal.R.anim.task_fragment_clear_top_open_exit);
} else {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_open_enter
: com.android.internal.R.anim.task_fragment_open_exit);
}
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are opening at the same time, the animation applied to each will be the same.
// Otherwise, we may see gap between the activities that are launching together.
@@ -195,16 +201,20 @@ class ActivityEmbeddingAnimationSpec {
}
@NonNull
Animation loadCloseAnimation(@NonNull TransitionInfo.Change change,
@NonNull Rect wholeAnimationBounds) {
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
// TODO(b/207070762):
// 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
// 2. Implement edgeExtension version
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? R.anim.task_fragment_close_enter
: R.anim.task_fragment_close_exit);
// TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_close_enter
: com.android.internal.R.anim.task_fragment_clear_top_close_exit);
} else {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_close_enter
: com.android.internal.R.anim.task_fragment_close_exit);
}
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are closing at the same time, the animation applied to each will be the same.
// Otherwise, we may see gap between the activities that are finishing together.
@@ -213,4 +223,11 @@ class ActivityEmbeddingAnimationSpec {
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
return animation;
}
private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
mTransitionAnimation);
return a != null && a.getShowBackdrop();
}
}

View File

@@ -18,13 +18,11 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -43,7 +41,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -59,6 +56,10 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -74,7 +75,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -84,7 +84,6 @@ import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
@@ -122,21 +121,6 @@ import java.util.function.Consumer;
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static final int MAX_ANIMATION_DURATION = 3000;
/**
* Restrict ability of activities overriding transition animation in a way such that
* an activity can do it only when the transition happens within a same task.
*
* @see android.app.Activity#overridePendingTransition(int, int)
*/
private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
"persist.wm.disable_custom_task_animation";
/**
* @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
*/
static boolean sDisableCustomTaskAnimationProperty =
SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
private final Context mContext;
@@ -431,22 +415,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
cornerRadius = 0;
}
if (a.getShowBackdrop()) {
if (info.getAnimationOptions().getBackgroundColor() != 0) {
// If available use the background color provided through AnimationOptions
backgroundColorForTransition =
info.getAnimationOptions().getBackgroundColor();
} else if (a.getBackdropColor() != 0) {
// Otherwise fallback on the background color provided through the animation
// definition.
backgroundColorForTransition = a.getBackdropColor();
} else if (change.getBackgroundColor() != 0) {
// Otherwise default to the window's background color if provided through
// the theme as the background color for the animation - the top most window
// with a valid background color and showBackground set takes precedence.
backgroundColorForTransition = change.getBackgroundColor();
}
}
backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
backgroundColorForTransition);
boolean delayedEdgeExtension = false;
if (!isTask && a.hasExtension()) {
@@ -668,29 +638,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return edgeExtensionLayer;
}
private void addBackgroundToTransition(
@NonNull SurfaceControl rootLeash,
@ColorInt int color,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction
) {
final Color bgColor = Color.valueOf(color);
final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
.setName("Animation Background")
.setParent(rootLeash)
.setColorLayer()
.setOpaque(true)
.build();
startTransaction
.setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
.setColor(animationBackgroundSurface, colorArray)
.show(animationBackgroundSurface);
finishTransaction.remove(animationBackgroundSurface);
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -704,9 +651,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
@Nullable
private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
int wallpaperTransit) {
Animation a = null;
private Animation loadAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, int wallpaperTransit) {
Animation a;
final int type = info.getType();
final int flags = info.getFlags();
@@ -717,12 +664,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
final Rect endBounds = Transitions.isClosingType(changeMode)
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
final boolean isDream =
isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
if (info.isKeyguardGoingAway()) {
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -763,87 +708,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// This received a transferred starting window, so don't animate
return null;
} else {
int animAttr = 0;
boolean translucent = false;
if (isDream) {
if (type == TRANSIT_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
: R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
} else if (type == TRANSIT_CLOSE) {
animAttr = enter
? 0
: R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
}
} else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
: R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
} else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
} else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
: R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
} else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
} else if (type == TRANSIT_OPEN) {
// We will translucent open animation for translucent activities and tasks. Choose
// WindowAnimation_activityOpenEnterAnimation and set translucent here, then
// TransitionAnimation loads appropriate animation later.
if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
translucent = true;
}
if (isTask && !translucent) {
animAttr = enter
? R.styleable.WindowAnimation_taskOpenEnterAnimation
: R.styleable.WindowAnimation_taskOpenExitAnimation;
} else {
animAttr = enter
? R.styleable.WindowAnimation_activityOpenEnterAnimation
: R.styleable.WindowAnimation_activityOpenExitAnimation;
}
} else if (type == TRANSIT_TO_FRONT) {
animAttr = enter
? R.styleable.WindowAnimation_taskToFrontEnterAnimation
: R.styleable.WindowAnimation_taskToFrontExitAnimation;
} else if (type == TRANSIT_CLOSE) {
if (isTask) {
animAttr = enter
? R.styleable.WindowAnimation_taskCloseEnterAnimation
: R.styleable.WindowAnimation_taskCloseExitAnimation;
} else {
if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
translucent = true;
}
animAttr = enter
? R.styleable.WindowAnimation_activityCloseEnterAnimation
: R.styleable.WindowAnimation_activityCloseExitAnimation;
}
} else if (type == TRANSIT_TO_BACK) {
animAttr = enter
? R.styleable.WindowAnimation_taskToBackEnterAnimation
: R.styleable.WindowAnimation_taskToBackExitAnimation;
}
if (animAttr != 0) {
if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
a = mTransitionAnimation
.loadAnimationAttr(options.getPackageName(), options.getAnimations(),
animAttr, translucent);
} else {
a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
transitTypeToString(type),
enter);
a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
}
if (a != null) {

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Color;
import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.window.TransitionInfo;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
/**
* Restrict ability of activities overriding transition animation in a way such that
* an activity can do it only when the transition happens within a same task.
*
* @see android.app.Activity#overridePendingTransition(int, int)
*/
private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
"persist.wm.disable_custom_task_animation";
/**
* @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
*/
static final boolean sDisableCustomTaskAnimationProperty =
SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
/** Loads the animation that is defined through attribute id for the given transition. */
@Nullable
public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, int wallpaperTransit,
@NonNull TransitionAnimation transitionAnimation) {
final int type = info.getType();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
final boolean enter = Transitions.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
final boolean isDream =
isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
int animAttr = 0;
boolean translucent = false;
if (isDream) {
if (type == TRANSIT_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
: R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
} else if (type == TRANSIT_CLOSE) {
animAttr = enter
? 0
: R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
}
} else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
: R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
} else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
} else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
: R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
} else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
} else if (type == TRANSIT_OPEN) {
// We will translucent open animation for translucent activities and tasks. Choose
// WindowAnimation_activityOpenEnterAnimation and set translucent here, then
// TransitionAnimation loads appropriate animation later.
if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
translucent = true;
}
if (isTask && !translucent) {
animAttr = enter
? R.styleable.WindowAnimation_taskOpenEnterAnimation
: R.styleable.WindowAnimation_taskOpenExitAnimation;
} else {
animAttr = enter
? R.styleable.WindowAnimation_activityOpenEnterAnimation
: R.styleable.WindowAnimation_activityOpenExitAnimation;
}
} else if (type == TRANSIT_TO_FRONT) {
animAttr = enter
? R.styleable.WindowAnimation_taskToFrontEnterAnimation
: R.styleable.WindowAnimation_taskToFrontExitAnimation;
} else if (type == TRANSIT_CLOSE) {
if (isTask) {
animAttr = enter
? R.styleable.WindowAnimation_taskCloseEnterAnimation
: R.styleable.WindowAnimation_taskCloseExitAnimation;
} else {
if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
translucent = true;
}
animAttr = enter
? R.styleable.WindowAnimation_activityCloseEnterAnimation
: R.styleable.WindowAnimation_activityCloseExitAnimation;
}
} else if (type == TRANSIT_TO_BACK) {
animAttr = enter
? R.styleable.WindowAnimation_taskToBackEnterAnimation
: R.styleable.WindowAnimation_taskToBackExitAnimation;
}
Animation a = null;
if (animAttr != 0) {
if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
a = transitionAnimation
.loadAnimationAttr(options.getPackageName(), options.getAnimations(),
animAttr, translucent);
} else {
a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
transitTypeToString(type),
enter);
return a;
}
/**
* Gets the background {@link ColorInt} for the given transition animation if it is set.
*
* @param defaultColor {@link ColorInt} to return if there is no background color specified by
* the given transition animation.
*/
@ColorInt
public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Animation a,
@ColorInt int defaultColor) {
if (!a.getShowBackdrop()) {
return defaultColor;
}
if (info.getAnimationOptions() != null
&& info.getAnimationOptions().getBackgroundColor() != 0) {
// If available use the background color provided through AnimationOptions
return info.getAnimationOptions().getBackgroundColor();
} else if (a.getBackdropColor() != 0) {
// Otherwise fallback on the background color provided through the animation
// definition.
return a.getBackdropColor();
} else if (change.getBackgroundColor() != 0) {
// Otherwise default to the window's background color if provided through
// the theme as the background color for the animation - the top most window
// with a valid background color and showBackground set takes precedence.
return change.getBackgroundColor();
}
return defaultColor;
}
/**
* Adds the given {@code backgroundColor} as the background color to the transition animation.
*/
public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash,
@ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
if (backgroundColor == 0) {
// No background color.
return;
}
final Color bgColor = Color.valueOf(backgroundColor);
final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
.setName("Animation Background")
.setParent(rootLeash)
.setColorLayer()
.setOpaque(true)
.build();
startTransaction
.setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
.setColor(animationBackgroundSurface, colorArray)
.show(animationBackgroundSurface);
finishTransaction.remove(animationBackgroundSurface);
}
}