/* * 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_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.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 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 first unused bit. This can be used by remotes to attach custom flags to this change. */ public static final int FLAG_FIRST_CUSTOM = 1 << 8; /** @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_FIRST_CUSTOM }) public @interface ChangeFlags {} private final @TransitionType int mType; private final @TransitionFlags int mFlags; private final ArrayList 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 CREATOR = new Creator() { @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 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 ""; } } /** 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_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 indpendent. 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 int mStartRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED; private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; 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(); mRotationAnimation = 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 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; } /** @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 */ @NonNull public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } public boolean getAllowEnterPip() { return mAllowEnterPip; } public int getStartRotation() { return mStartRotation; } public int getEndRotation() { return mEndRotation; } /** @return the rotation animation. */ public int getRotationAnimation() { return mRotationAnimation; } /** @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(mRotationAnimation); } @NonNull public static final Creator CREATOR = new Creator() { @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 + "}"; } } /** 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 AnimationOptions(int type) { mType = type; } public AnimationOptions(Parcel in) { mType = in.readInt(); mEnterResId = in.readInt(); mExitResId = in.readInt(); mOverrideTaskTransition = in.readBoolean(); mPackageName = in.readString(); mTransitionBounds.readFromParcel(in); mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR); } public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId, int exitResId, boolean overrideTaskTransition) { AnimationOptions options = new AnimationOptions(ANIM_CUSTOM); options.mPackageName = packageName; options.mEnterResId = enterResId; options.mExitResId = exitResId; 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 makeThumnbnailAnimOptions(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 boolean getOverrideTaskTransition() { return mOverrideTaskTransition; } public String getPackageName() { return mPackageName; } public Rect getTransitionBounds() { return mTransitionBounds; } public HardwareBuffer getThumbnail() { return mThumbnail; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeInt(mEnterResId); dest.writeInt(mExitResId); dest.writeBoolean(mOverrideTaskTransition); dest.writeString(mPackageName); mTransitionBounds.writeToParcel(dest, flags); dest.writeTypedObject(mThumbnail, flags); } @NonNull public static final Creator CREATOR = new Creator() { @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 ""; } } @Override public String toString() { return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}"; } } }