Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc047eee authored by Evan Rosky's avatar Evan Rosky
Browse files

Differentiate seamless rotations in shell transitions

Shell owns the whole display rotation animation now. So, we
can just provide enough information for it to decide when
seamless is requested/appropriate. The default behavior (when
not explicitly animating) is already a jump-cut/seamless, so
just skip the rotation animation if we are doing seamless

Bug: 194693472
Test: atest SeamlessAppRotationTest ShellTransitionTests
Change-Id: I64748f5910c04784fd9818818b029fa5f1339c6c
parent bfe16934
Loading
Loading
Loading
Loading
+35 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ 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_NONE;
@@ -94,8 +95,16 @@ public final class TransitionInfo implements Parcelable {
    /** 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 << 7;
    public static final int FLAG_FIRST_CUSTOM = 1 << 8;

    /** @hide */
    @IntDef(prefix = { "FLAG_" }, value = {
@@ -107,6 +116,7 @@ public final class TransitionInfo implements Parcelable {
            FLAG_IS_VOICE_INTERACTION,
            FLAG_IS_DISPLAY,
            FLAG_OCCLUDES_KEYGUARD,
            FLAG_DISPLAY_HAS_ALERT_WINDOWS,
            FLAG_FIRST_CUSTOM
    })
    public @interface ChangeFlags {}
@@ -209,6 +219,10 @@ public final class TransitionInfo implements Parcelable {
        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;
@@ -290,6 +304,9 @@ public final class TransitionInfo implements Parcelable {
        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");
        }
@@ -337,6 +354,7 @@ public final class TransitionInfo implements Parcelable {
        private ActivityManager.RunningTaskInfo mTaskInfo = null;
        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;
@@ -356,6 +374,7 @@ public final class TransitionInfo implements Parcelable {
            mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
            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. */
@@ -402,6 +421,14 @@ public final class TransitionInfo implements Parcelable {
            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() {
@@ -473,6 +500,11 @@ public final class TransitionInfo implements Parcelable {
            return mEndRotation;
        }

        /** @return the rotation animation. */
        public int getRotationAnimation() {
            return mRotationAnimation;
        }

        /** @hide */
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -487,6 +519,7 @@ public final class TransitionInfo implements Parcelable {
            dest.writeTypedObject(mTaskInfo, flags);
            dest.writeInt(mStartRotation);
            dest.writeInt(mEndRotation);
            dest.writeInt(mRotationAnimation);
        }

        @NonNull
@@ -514,7 +547,7 @@ public final class TransitionInfo implements Parcelable {
            return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
                    + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
                    + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
                    + mStartRotation + "->" + mEndRotation + "}";
                    + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}";
        }
    }

+38 −1
Original line number Diff line number Diff line
@@ -82,6 +82,9 @@ public class DisplayLayout {
    private boolean mHasNavigationBar = false;
    private boolean mHasStatusBar = false;
    private int mNavBarFrameHeight = 0;
    private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
    private boolean mNavigationBarCanMove = false;
    private boolean mReverseDefaultRotation = false;

    @Override
    public boolean equals(Object o) {
@@ -98,6 +101,10 @@ public class DisplayLayout {
                && Objects.equals(mStableInsets, other.mStableInsets)
                && mHasNavigationBar == other.mHasNavigationBar
                && mHasStatusBar == other.mHasStatusBar
                && mAllowSeamlessRotationDespiteNavBarMoving
                        == other.mAllowSeamlessRotationDespiteNavBarMoving
                && mNavigationBarCanMove == other.mNavigationBarCanMove
                && mReverseDefaultRotation == other.mReverseDefaultRotation
                && mNavBarFrameHeight == other.mNavBarFrameHeight;
    }

@@ -105,7 +112,8 @@ public class DisplayLayout {
    public int hashCode() {
        return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
                mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
                mNavBarFrameHeight);
                mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
                mNavigationBarCanMove, mReverseDefaultRotation);
    }

    /**
@@ -150,6 +158,9 @@ public class DisplayLayout {
        mDensityDpi = dl.mDensityDpi;
        mHasNavigationBar = dl.mHasNavigationBar;
        mHasStatusBar = dl.mHasStatusBar;
        mAllowSeamlessRotationDespiteNavBarMoving = dl.mAllowSeamlessRotationDespiteNavBarMoving;
        mNavigationBarCanMove = dl.mNavigationBarCanMove;
        mReverseDefaultRotation = dl.mReverseDefaultRotation;
        mNavBarFrameHeight = dl.mNavBarFrameHeight;
        mNonDecorInsets.set(dl.mNonDecorInsets);
        mStableInsets.set(dl.mStableInsets);
@@ -165,6 +176,10 @@ public class DisplayLayout {
        mDensityDpi = info.logicalDensityDpi;
        mHasNavigationBar = hasNavigationBar;
        mHasStatusBar = hasStatusBar;
        mAllowSeamlessRotationDespiteNavBarMoving = res.getBoolean(
            R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
        mNavigationBarCanMove = res.getBoolean(R.bool.config_navBarCanMove);
        mReverseDefaultRotation = res.getBoolean(R.bool.config_reverseDefaultRotation);
        recalcInsets(res);
    }

@@ -249,6 +264,28 @@ public class DisplayLayout {
        return mNavBarFrameHeight;
    }

    /** @return whether we can seamlessly rotate even if nav-bar can change sides. */
    public boolean allowSeamlessRotationDespiteNavBarMoving() {
        return mAllowSeamlessRotationDespiteNavBarMoving;
    }

    /** @return whether the navigation bar will change sides during rotation. */
    public boolean navigationBarCanMove() {
        return mNavigationBarCanMove;
    }

    /** @return the rotation that would make the physical display "upside down". */
    public int getUpsideDownRotation() {
        boolean displayHardwareIsLandscape = mWidth > mHeight;
        if ((mRotation % 2) != 0) {
            displayHardwareIsLandscape = !displayHardwareIsLandscape;
        }
        if (displayHardwareIsLandscape) {
            return mReverseDefaultRotation ? Surface.ROTATION_270 : Surface.ROTATION_90;
        }
        return Surface.ROTATION_180;
    }

    /** Gets the orientation of this layout */
    public int getOrientation() {
        return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+126 −6
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ 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.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
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_KEYGUARD_GOING_AWAY;
@@ -31,8 +35,10 @@ 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.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -68,9 +74,12 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -97,6 +106,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);

    private final TransactionPool mTransactionPool;
    private final DisplayController mDisplayController;
    private final Context mContext;
    private final ShellExecutor mMainExecutor;
    private final ShellExecutor mAnimExecutor;
@@ -114,8 +124,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {

    private ScreenRotationAnimation mRotationAnimation;

    DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
    DefaultTransitionHandler(@NonNull DisplayController displayController,
            @NonNull TransactionPool transactionPool, Context context,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
        mDisplayController = displayController;
        mTransactionPool = transactionPool;
        mContext = context;
        mMainExecutor = mainExecutor;
@@ -126,6 +138,110 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
        AttributeCache.init(context);
    }

    @VisibleForTesting
    static boolean isRotationSeamless(@NonNull TransitionInfo info,
            DisplayController displayController) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "Display is rotating, check if it should be seamless.");
        boolean checkedDisplayLayout = false;
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);

            // Only look at changing things. showing/hiding don't need to rotate.
            if (change.getMode() != TRANSIT_CHANGE) continue;

            // This container isn't rotating, so we can ignore it.
            if (change.getEndRotation() == change.getStartRotation()) continue;

            if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
                // In the presence of System Alert windows we can not seamlessly rotate.
                if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                            "  display has system alert windows, so not seamless.");
                    return false;
                }
            } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
                if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                            "  wallpaper is participating but isn't seamless.");
                    return false;
                }
            } else if (change.getTaskInfo() != null) {
                // We only enable seamless rotation if all the visible task windows requested it.
                if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                            "  task %s isn't requesting seamless, so not seamless.",
                            change.getTaskInfo().taskId);
                    return false;
                }

                // This is the only way to get display-id currently, so we will check display
                // capabilities here
                if (!checkedDisplayLayout) {
                    // only need to check display once.
                    checkedDisplayLayout = true;
                    final DisplayLayout displayLayout = displayController.getDisplayLayout(
                            change.getTaskInfo().displayId);
                    // For the upside down rotation we don't rotate seamlessly as the navigation
                    // bar moves position. Note most apps (using orientation:sensor or user as
                    // opposed to fullSensor) will not enter the reverse portrait orientation, so
                    // actually the orientation won't change at all.
                    int upsideDownRotation = displayLayout.getUpsideDownRotation();
                    if (change.getStartRotation() == upsideDownRotation
                            || change.getEndRotation() == upsideDownRotation) {
                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                                "  rotation involves upside-down portrait, so not seamless.");
                        return false;
                    }

                    // If the navigation bar can't change sides, then it will jump when we change
                    // orientations and we don't rotate seamlessly - unless that is allowed, eg.
                    // with gesture navigation where the navbar is low-profile enough that this
                    // isn't very noticeable.
                    if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
                            && (!(displayLayout.navigationBarCanMove()
                                    && (change.getStartAbsBounds().width()
                                            != change.getStartAbsBounds().height())))) {
                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                                "  nav bar changes sides, so not seamless.");
                        return false;
                    }
                }
            }
        }

        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
        return true;
    }

    /**
     * Gets the rotation animation for the topmost task. Assumes that seamless is checked
     * elsewhere, so it will default SEAMLESS to ROTATE.
     */
    private int getRotationAnimation(@NonNull TransitionInfo info) {
        // Traverse in top-to-bottom order so that the first task is top-most
        for (int i = 0; i < info.getChanges().size(); ++i) {
            final TransitionInfo.Change change = info.getChanges().get(i);

            // Only look at changing things. showing/hiding don't need to rotate.
            if (change.getMode() != TRANSIT_CHANGE) continue;

            // This container isn't rotating, so we can ignore it.
            if (change.getEndRotation() == change.getStartRotation()) continue;

            if (change.getTaskInfo() != null) {
                final int anim = change.getRotationAnimation();
                if (anim == ROTATION_ANIMATION_UNSPECIFIED
                        // Fallback animation for seamless should also be default.
                        || anim == ROTATION_ANIMATION_SEAMLESS) {
                    return ROTATION_ANIMATION_ROTATE;
                }
                return anim;
            }
        }
        return ROTATION_ANIMATION_ROTATE;
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
@@ -168,12 +284,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE
                    && (change.getEndRotation() != change.getStartRotation())
                    && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
                boolean isSeamless = isRotationSeamless(info, mDisplayController);
                final int anim = getRotationAnimation(info);
                if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
                    mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
                            mTransactionPool, startTransaction, change, info.getRootLeash());
                    mRotationAnimation.startAnimation(animations, onAnimFinish,
                            mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
                    continue;
                }
            }

            if (change.getMode() == TRANSIT_CHANGE) {
                // No default animation for this, so just update bounds/position.
+5 −3
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -113,15 +114,16 @@ public class Transitions implements RemoteCallable<Transitions> {
    private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();

    public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
            @NonNull Context context, @NonNull ShellExecutor mainExecutor,
            @NonNull ShellExecutor animExecutor) {
            @NonNull DisplayController displayController, @NonNull Context context,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
        mOrganizer = organizer;
        mContext = context;
        mMainExecutor = mainExecutor;
        mAnimExecutor = animExecutor;
        mPlayerImpl = new TransitionPlayerImpl();
        // The very last handler (0 in the list) should be the default one.
        mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
        mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
                animExecutor));
        // Next lowest priority is remote transitions.
        mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
        mHandlers.add(mRemoteTransitionHandler);
+159 −14

File changed.

Preview size limit exceeded, changes collapsed.

Loading