blob: c81184fb238304b9017e0655873d0b412cc7fbbd [file] [log] [blame]
/*
* Copyright (C) 2020 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 android.window;
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_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.ROTATION_UNDEFINED;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
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.TransitionFlags;
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
/**
* Used to communicate information about what is changing during a transition to a TransitionPlayer.
* @hide
*/
public final class TransitionInfo implements Parcelable {
/**
* Modes are only a sub-set of all the transit-types since they are per-container
* @hide
*/
@IntDef(prefix = { "TRANSIT_" }, value = {
TRANSIT_NONE,
TRANSIT_OPEN,
TRANSIT_CLOSE,
// Note: to_front/to_back really mean show/hide respectively at the container level.
TRANSIT_TO_FRONT,
TRANSIT_TO_BACK,
TRANSIT_CHANGE
})
public @interface TransitionMode {}
/** No flags */
public static final int FLAG_NONE = 0;
/** The container shows the wallpaper behind it. */
public static final int FLAG_SHOW_WALLPAPER = 1;
/** The container IS the wallpaper. */
public static final int FLAG_IS_WALLPAPER = 1 << 1;
/** The container is translucent. */
public static final int FLAG_TRANSLUCENT = 1 << 2;
// TODO: remove when starting-window is moved to Task
/** The container is the recipient of a transferred starting-window */
public static final int FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT = 1 << 3;
/** The container has voice session. */
public static final int FLAG_IS_VOICE_INTERACTION = 1 << 4;
/** The container is the display. */
public static final int FLAG_IS_DISPLAY = 1 << 5;
/** The container can show on top of lock screen. */
public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6;
/**
* Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
* used to prevent seamless rotation.
* TODO(b/194540864): Once we can include all windows in transition, then replace this with
* something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
*/
public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
/** The container is an input-method window. */
public static final int FLAG_IS_INPUT_METHOD = 1 << 8;
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
public static final int FLAG_FIRST_CUSTOM = 1 << 9;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
FLAG_SHOW_WALLPAPER,
FLAG_IS_WALLPAPER,
FLAG_TRANSLUCENT,
FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT,
FLAG_IS_VOICE_INTERACTION,
FLAG_IS_DISPLAY,
FLAG_OCCLUDES_KEYGUARD,
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
private final @TransitionType int mType;
private final @TransitionFlags int mFlags;
private final ArrayList<Change> mChanges = new ArrayList<>();
private SurfaceControl mRootLeash;
private final Point mRootOffset = new Point();
private AnimationOptions mOptions;
/** @hide */
public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) {
mType = type;
mFlags = flags;
}
private TransitionInfo(Parcel in) {
mType = in.readInt();
mFlags = in.readInt();
in.readTypedList(mChanges, Change.CREATOR);
mRootLeash = new SurfaceControl();
mRootLeash.readFromParcel(in);
mRootOffset.readFromParcel(in);
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
}
@Override
/** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mFlags);
dest.writeTypedList(mChanges);
mRootLeash.writeToParcel(dest, flags);
mRootOffset.writeToParcel(dest, flags);
dest.writeTypedObject(mOptions, flags);
}
@NonNull
public static final Creator<TransitionInfo> CREATOR =
new Creator<TransitionInfo>() {
@Override
public TransitionInfo createFromParcel(Parcel in) {
return new TransitionInfo(in);
}
@Override
public TransitionInfo[] newArray(int size) {
return new TransitionInfo[size];
}
};
@Override
/** @hide */
public int describeContents() {
return 0;
}
/** @see #getRootLeash() */
public void setRootLeash(@NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
mRootLeash = leash;
mRootOffset.set(offsetLeft, offsetTop);
}
public void setAnimationOptions(AnimationOptions options) {
mOptions = options;
}
public @TransitionType int getType() {
return mType;
}
public int getFlags() {
return mFlags;
}
/**
* @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
* participants to animate within. This will generally be placed at the highest-z-order
* shared ancestor of all participants. While this is non-null, it's possible for the rootleash
* to be invalid if the transition is a no-op.
*/
@NonNull
public SurfaceControl getRootLeash() {
if (mRootLeash == null) {
throw new IllegalStateException("Trying to get a leash which wasn't set");
}
return mRootLeash;
}
/** @return the offset (relative to the screen) of the root leash. */
@NonNull
public Point getRootOffset() {
return mRootOffset;
}
public AnimationOptions getAnimationOptions() {
return mOptions;
}
/**
* @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom
* in Z (meaning index 0 is the top-most container).
*/
@NonNull
public List<Change> getChanges() {
return mChanges;
}
/**
* @return the Change that a window is undergoing or {@code null} if not directly
* represented.
*/
@Nullable
public Change getChange(@NonNull WindowContainerToken token) {
for (int i = mChanges.size() - 1; i >= 0; --i) {
if (token.equals(mChanges.get(i).mContainer)) {
return mChanges.get(i);
}
}
return null;
}
/**
* Add a {@link Change} to this transition.
*/
public void addChange(@NonNull Change change) {
mChanges.add(change);
}
/**
* Whether this transition includes keyguard going away.
*/
public boolean isKeyguardGoingAway() {
return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags)
+ " ro=" + mRootOffset + " c=[");
for (int i = 0; i < mChanges.size(); ++i) {
if (i > 0) {
sb.append(',');
}
sb.append(mChanges.get(i));
}
sb.append("]}");
return sb.toString();
}
/** Converts a transition mode/action to its string representation. */
@NonNull
public static String modeToString(@TransitionMode int mode) {
switch(mode) {
case TRANSIT_NONE: return "NONE";
case TRANSIT_OPEN: return "OPEN";
case TRANSIT_CLOSE: return "CLOSE";
case TRANSIT_TO_FRONT: return "SHOW";
case TRANSIT_TO_BACK: return "HIDE";
case TRANSIT_CHANGE: return "CHANGE";
default: return "<unknown:" + mode + ">";
}
}
/** Converts change flags into a string representation. */
@NonNull
public static String flagsToString(@ChangeFlags int flags) {
if (flags == 0) return "NONE";
final StringBuilder sb = new StringBuilder();
if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
sb.append("SHOW_WALLPAPER");
}
if ((flags & FLAG_IS_WALLPAPER) != 0) {
sb.append("IS_WALLPAPER");
}
if ((flags & FLAG_IS_INPUT_METHOD) != 0) {
sb.append("IS_INPUT_METHOD");
}
if ((flags & FLAG_TRANSLUCENT) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "TRANSLUCENT");
}
if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "STARTING_WINDOW_TRANSFER");
}
if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION");
}
if ((flags & FLAG_IS_DISPLAY) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY");
}
if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD");
}
if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS");
}
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
}
return sb.toString();
}
/**
* Indication that `change` is independent of parents (ie. it has a different type of
* transition vs. "going along for the ride")
*/
public static boolean isIndependent(@NonNull TransitionInfo.Change change,
@NonNull TransitionInfo info) {
// If the change has no parent (it is root), then it is independent
if (change.getParent() == null) return true;
// non-visibility changes will just be folded into the parent change, so they aren't
// independent either.
if (change.getMode() == TRANSIT_CHANGE) return false;
TransitionInfo.Change parentChg = info.getChange(change.getParent());
while (parentChg != null) {
// If the parent is a visibility change, it will include the results of all child
// changes into itself, so none of its children can be independent.
if (parentChg.getMode() != TRANSIT_CHANGE) return false;
// If there are no more parents left, then all the parents, so far, have not been
// visibility changes which means this change is independent.
if (parentChg.getParent() == null) return true;
parentChg = info.getChange(parentChg.getParent());
}
return false;
}
/** Represents the change a WindowContainer undergoes during a transition */
public static final class Change implements Parcelable {
private final WindowContainerToken mContainer;
private WindowContainerToken mParent;
private final SurfaceControl mLeash;
private @TransitionMode int mMode = TRANSIT_NONE;
private @ChangeFlags int mFlags = FLAG_NONE;
private final Rect mStartAbsBounds = new Rect();
private final Rect mEndAbsBounds = new Rect();
private final Point mEndRelOffset = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
private boolean mAllowEnterPip;
private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;
/**
* The end rotation of the top activity after fixed rotation is finished. If the top
* activity is not in fixed rotation, it will be {@link ROTATION_UNDEFINED}.
*/
private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED;
private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
private @ColorInt int mBackgroundColor;
public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
mContainer = container;
mLeash = leash;
}
private Change(Parcel in) {
mContainer = in.readTypedObject(WindowContainerToken.CREATOR);
mParent = in.readTypedObject(WindowContainerToken.CREATOR);
mLeash = new SurfaceControl();
mLeash.readFromParcel(in);
mMode = in.readInt();
mFlags = in.readInt();
mStartAbsBounds.readFromParcel(in);
mEndAbsBounds.readFromParcel(in);
mEndRelOffset.readFromParcel(in);
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
mAllowEnterPip = in.readBoolean();
mStartRotation = in.readInt();
mEndRotation = in.readInt();
mEndFixedRotation = in.readInt();
mRotationAnimation = in.readInt();
mBackgroundColor = in.readInt();
}
/** Sets the parent of this change's container. The parent must be a participant or null. */
public void setParent(@Nullable WindowContainerToken parent) {
mParent = parent;
}
/** Sets the transition mode for this change */
public void setMode(@TransitionMode int mode) {
mMode = mode;
}
/** Sets the flags for this change */
public void setFlags(@ChangeFlags int flags) {
mFlags = flags;
}
/** Sets the bounds this container occupied before the change in screen space */
public void setStartAbsBounds(@Nullable Rect rect) {
mStartAbsBounds.set(rect);
}
/** Sets the bounds this container will occupy after the change in screen space */
public void setEndAbsBounds(@Nullable Rect rect) {
mEndAbsBounds.set(rect);
}
/** Sets the offset of this container from its parent surface */
public void setEndRelOffset(int left, int top) {
mEndRelOffset.set(left, top);
}
/**
* Sets the taskinfo of this container if this is a task. WARNING: this takes the
* reference, so don't modify it afterwards.
*/
public void setTaskInfo(@Nullable ActivityManager.RunningTaskInfo taskInfo) {
mTaskInfo = taskInfo;
}
/** Sets the allowEnterPip flag which represents AppOpsManager check on PiP permission */
public void setAllowEnterPip(boolean allowEnterPip) {
mAllowEnterPip = allowEnterPip;
}
/** Sets the start and end rotation of this container. */
public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
mStartRotation = start;
mEndRotation = end;
}
/** Sets end rotation that top activity will be launched to after fixed rotation. */
public void setEndFixedRotation(@Surface.Rotation int endFixedRotation) {
mEndFixedRotation = endFixedRotation;
}
/**
* Sets the app-requested animation type for rotation. Will be one of the
* ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams};
*/
public void setRotationAnimation(int anim) {
mRotationAnimation = anim;
}
/** Sets the background color of this change's container. */
public void setBackgroundColor(@ColorInt int backgroundColor) {
mBackgroundColor = backgroundColor;
}
/** @return the container that is changing. May be null if non-remotable (eg. activity) */
@Nullable
public WindowContainerToken getContainer() {
return mContainer;
}
/**
* @return the parent of the changing container. This is the parent within the participants,
* not necessarily the actual parent.
*/
@Nullable
public WindowContainerToken getParent() {
return mParent;
}
/** @return which action this change represents. */
public @TransitionMode int getMode() {
return mMode;
}
/** @return the flags for this change. */
public @ChangeFlags int getFlags() {
return mFlags;
}
/**
* @return the bounds of the container before the change. It may be empty if the container
* is coming into existence.
*/
@NonNull
public Rect getStartAbsBounds() {
return mStartAbsBounds;
}
/**
* @return the bounds of the container after the change. It may be empty if the container
* is disappearing.
*/
@NonNull
public Rect getEndAbsBounds() {
return mEndAbsBounds;
}
/**
* @return the offset of the container's surface from its parent surface after the change.
*/
@NonNull
public Point getEndRelOffset() {
return mEndRelOffset;
}
/** @return the leash or surface to animate for this container */
@NonNull
public SurfaceControl getLeash() {
return mLeash;
}
/** @return the task info or null if this isn't a task */
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo() {
return mTaskInfo;
}
public boolean getAllowEnterPip() {
return mAllowEnterPip;
}
@Surface.Rotation
public int getStartRotation() {
return mStartRotation;
}
@Surface.Rotation
public int getEndRotation() {
return mEndRotation;
}
@Surface.Rotation
public int getEndFixedRotation() {
return mEndFixedRotation;
}
/** @return the rotation animation. */
public int getRotationAnimation() {
return mRotationAnimation;
}
/** @return get the background color of this change's container. */
@ColorInt
public int getBackgroundColor() {
return mBackgroundColor;
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mContainer, flags);
dest.writeTypedObject(mParent, flags);
mLeash.writeToParcel(dest, flags);
dest.writeInt(mMode);
dest.writeInt(mFlags);
mStartAbsBounds.writeToParcel(dest, flags);
mEndAbsBounds.writeToParcel(dest, flags);
mEndRelOffset.writeToParcel(dest, flags);
dest.writeTypedObject(mTaskInfo, flags);
dest.writeBoolean(mAllowEnterPip);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
dest.writeInt(mEndFixedRotation);
dest.writeInt(mRotationAnimation);
dest.writeInt(mBackgroundColor);
}
@NonNull
public static final Creator<Change> CREATOR =
new Creator<Change>() {
@Override
public Change createFromParcel(Parcel in) {
return new Change(in);
}
@Override
public Change[] newArray(int size) {
return new Change[size];
}
};
/** @hide */
@Override
public int describeContents() {
return 0;
}
@Override
public String toString() {
return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
+ mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
+ mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation
+ " endFixedRotation=" + mEndFixedRotation + "}";
}
}
/** Represents animation options during a transition */
public static final class AnimationOptions implements Parcelable {
private int mType;
private int mEnterResId;
private int mExitResId;
private boolean mOverrideTaskTransition;
private String mPackageName;
private final Rect mTransitionBounds = new Rect();
private HardwareBuffer mThumbnail;
private int mAnimations;
private @ColorInt int mBackgroundColor;
private AnimationOptions(int type) {
mType = type;
}
public AnimationOptions(Parcel in) {
mType = in.readInt();
mEnterResId = in.readInt();
mExitResId = in.readInt();
mBackgroundColor = in.readInt();
mOverrideTaskTransition = in.readBoolean();
mPackageName = in.readString();
mTransitionBounds.readFromParcel(in);
mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
mAnimations = in.readInt();
}
public static AnimationOptions makeAnimOptionsFromLayoutParameters(
WindowManager.LayoutParams lp) {
AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
options.mPackageName = lp.packageName;
options.mAnimations = lp.windowAnimations;
return options;
}
public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
options.mPackageName = packageName;
options.mEnterResId = enterResId;
options.mExitResId = exitResId;
options.mBackgroundColor = backgroundColor;
options.mOverrideTaskTransition = overrideTaskTransition;
return options;
}
public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width,
int height) {
AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
return options;
}
public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
int height) {
AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
return options;
}
public static AnimationOptions makeThumbnailAnimOptions(HardwareBuffer srcThumb,
int startX, int startY, boolean scaleUp) {
AnimationOptions options = new AnimationOptions(
scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
options.mTransitionBounds.set(startX, startY, startX, startY);
options.mThumbnail = srcThumb;
return options;
}
public static AnimationOptions makeCrossProfileAnimOptions() {
AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
return options;
}
public int getType() {
return mType;
}
public int getEnterResId() {
return mEnterResId;
}
public int getExitResId() {
return mExitResId;
}
public @ColorInt int getBackgroundColor() {
return mBackgroundColor;
}
public boolean getOverrideTaskTransition() {
return mOverrideTaskTransition;
}
public String getPackageName() {
return mPackageName;
}
public Rect getTransitionBounds() {
return mTransitionBounds;
}
public HardwareBuffer getThumbnail() {
return mThumbnail;
}
public int getAnimations() {
return mAnimations;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mEnterResId);
dest.writeInt(mExitResId);
dest.writeInt(mBackgroundColor);
dest.writeBoolean(mOverrideTaskTransition);
dest.writeString(mPackageName);
mTransitionBounds.writeToParcel(dest, flags);
dest.writeTypedObject(mThumbnail, flags);
dest.writeInt(mAnimations);
}
@NonNull
public static final Creator<AnimationOptions> CREATOR =
new Creator<AnimationOptions>() {
@Override
public AnimationOptions createFromParcel(Parcel in) {
return new AnimationOptions(in);
}
@Override
public AnimationOptions[] newArray(int size) {
return new AnimationOptions[size];
}
};
/** @hide */
@Override
public int describeContents() {
return 0;
}
@NonNull
private static String typeToString(int mode) {
switch(mode) {
case ANIM_CUSTOM: return "ANIM_CUSTOM";
case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
default: return "<unknown:" + mode + ">";
}
}
@Override
public String toString() {
return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
+ " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
}
}
}