blob: 19d8384ace412507f0674ec4b8d571f4d31b325a [file] [log] [blame]
/*
* 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.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_STARTING_WINDOW_TRANSFER_RECIPIENT;
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.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Shader;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.ScreenCapture;
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;
import com.android.wm.shell.util.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
/** 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, boolean isDreamTransition) {
final int type = info.getType();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
final boolean enter = TransitionUtil.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
int animAttr = 0;
boolean translucent = false;
if (isDreamTransition) {
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 ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
translucent = true;
}
if (isTask && !translucent) {
animAttr = enter
? R.styleable.WindowAnimation_taskCloseEnterAnimation
: R.styleable.WindowAnimation_taskCloseExitAnimation;
} else {
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 && !isTask) {
final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition =
getCustomActivityTransition(animAttr, options);
if (customTransition != null) {
a = loadCustomActivityTransition(
customTransition, options, enter, transitionAnimation);
} else {
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;
}
static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition(
int animAttr, TransitionInfo.AnimationOptions options) {
boolean isOpen = false;
switch (animAttr) {
case R.styleable.WindowAnimation_activityOpenEnterAnimation:
case R.styleable.WindowAnimation_activityOpenExitAnimation:
isOpen = true;
break;
case R.styleable.WindowAnimation_activityCloseEnterAnimation:
case R.styleable.WindowAnimation_activityCloseExitAnimation:
break;
default:
return null;
}
return options.getCustomActivityTransition(isOpen);
}
static Animation loadCustomActivityTransition(
@NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
TransitionInfo.AnimationOptions options, boolean enter,
TransitionAnimation transitionAnimation) {
final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
enter ? transitionAnim.getCustomEnterResId()
: transitionAnim.getCustomExitResId());
if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
}
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);
}
/**
* Adds edge extension surface to the given {@code change} for edge extension animation.
*/
public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
@NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
// Do not create edge extension surface for transfer starting window change.
// The app surface could be empty thus nothing can draw on the hardware renderer, which will
// block this thread when calling Surface#unlockCanvasAndPost.
if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return;
}
final Transformation transformationAtStart = new Transformation();
a.getTransformationAt(0, transformationAtStart);
final Transformation transformationAtEnd = new Transformation();
a.getTransformationAt(1, transformationAtEnd);
// We want to create an extension surface that is the maximal size and the animation will
// take care of cropping any part that overflows.
final Insets maxExtensionInsets = Insets.min(
transformationAtStart.getInsets(), transformationAtEnd.getInsets());
final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
change.getEndAbsBounds().height());
final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
change.getEndAbsBounds().width());
if (maxExtensionInsets.left < 0) {
final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
final Rect extensionRect = new Rect(0, 0,
-maxExtensionInsets.left, targetSurfaceHeight);
final int xPos = maxExtensionInsets.left;
final int yPos = 0;
createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
"Left Edge Extension", startTransaction, finishTransaction);
}
if (maxExtensionInsets.top < 0) {
final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
final Rect extensionRect = new Rect(0, 0,
targetSurfaceWidth, -maxExtensionInsets.top);
final int xPos = 0;
final int yPos = maxExtensionInsets.top;
createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
"Top Edge Extension", startTransaction, finishTransaction);
}
if (maxExtensionInsets.right < 0) {
final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
targetSurfaceWidth, targetSurfaceHeight);
final Rect extensionRect = new Rect(0, 0,
-maxExtensionInsets.right, targetSurfaceHeight);
final int xPos = targetSurfaceWidth;
final int yPos = 0;
createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
"Right Edge Extension", startTransaction, finishTransaction);
}
if (maxExtensionInsets.bottom < 0) {
final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
targetSurfaceWidth, targetSurfaceHeight);
final Rect extensionRect = new Rect(0, 0,
targetSurfaceWidth, -maxExtensionInsets.bottom);
final int xPos = maxExtensionInsets.left;
final int yPos = targetSurfaceHeight;
createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
"Bottom Edge Extension", startTransaction, finishTransaction);
}
}
/**
* Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
* animation.
*/
private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
@NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
@NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
.setName(layerName)
.setParent(surfaceToExtend)
.setHidden(true)
.setCallsite("TransitionAnimationHelper#createExtensionSurface")
.setOpaque(true)
.setBufferSize(extensionRect.width(), extensionRect.height())
.build();
final ScreenCapture.LayerCaptureArgs captureArgs =
new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
.setSourceCrop(edgeBounds)
.setFrameScale(1)
.setPixelFormat(PixelFormat.RGBA_8888)
.setChildrenOnly(true)
.setAllowProtected(true)
.setCaptureSecureLayers(true)
.build();
final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
ScreenCapture.captureLayers(captureArgs);
if (edgeBuffer == null) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Failed to capture edge of window.");
return null;
}
final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
final Paint paint = new Paint();
paint.setShader(shader);
final Surface surface = new Surface(edgeExtensionLayer);
final Canvas c = surface.lockHardwareCanvas();
c.drawRect(extensionRect, paint);
surface.unlockCanvasAndPost(c);
surface.release();
startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
startTransaction.setVisibility(edgeExtensionLayer, true);
finishTransaction.remove(edgeExtensionLayer);
return edgeExtensionLayer;
}
}