Merge "Differentiate seamless rotations in shell transitions" into sc-v2-dev

This commit is contained in:
Evan Rosky
2021-08-06 00:15:30 +00:00
committed by Android (Google) Code Review
7 changed files with 391 additions and 28 deletions

View File

@@ -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_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 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_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE; 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. */ /** The container can show on top of lock screen. */
public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6; 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. */ /** 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 */ /** @hide */
@IntDef(prefix = { "FLAG_" }, value = { @IntDef(prefix = { "FLAG_" }, value = {
@@ -107,6 +116,7 @@ public final class TransitionInfo implements Parcelable {
FLAG_IS_VOICE_INTERACTION, FLAG_IS_VOICE_INTERACTION,
FLAG_IS_DISPLAY, FLAG_IS_DISPLAY,
FLAG_OCCLUDES_KEYGUARD, FLAG_OCCLUDES_KEYGUARD,
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_FIRST_CUSTOM FLAG_FIRST_CUSTOM
}) })
public @interface ChangeFlags {} public @interface ChangeFlags {}
@@ -209,6 +219,10 @@ public final class TransitionInfo implements Parcelable {
return mOptions; 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 @NonNull
public List<Change> getChanges() { public List<Change> getChanges() {
return mChanges; return mChanges;
@@ -290,6 +304,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) { if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD"); 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) { if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM"); sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
} }
@@ -337,6 +354,7 @@ public final class TransitionInfo implements Parcelable {
private ActivityManager.RunningTaskInfo mTaskInfo = null; private ActivityManager.RunningTaskInfo mTaskInfo = null;
private int mStartRotation = ROTATION_UNDEFINED; private int mStartRotation = ROTATION_UNDEFINED;
private int mEndRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED;
private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
mContainer = container; mContainer = container;
@@ -356,6 +374,7 @@ public final class TransitionInfo implements Parcelable {
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
mStartRotation = in.readInt(); mStartRotation = in.readInt();
mEndRotation = in.readInt(); mEndRotation = in.readInt();
mRotationAnimation = in.readInt();
} }
/** Sets the parent of this change's container. The parent must be a participant or null. */ /** 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; 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) */ /** @return the container that is changing. May be null if non-remotable (eg. activity) */
@Nullable @Nullable
public WindowContainerToken getContainer() { public WindowContainerToken getContainer() {
@@ -473,6 +500,11 @@ public final class TransitionInfo implements Parcelable {
return mEndRotation; return mEndRotation;
} }
/** @return the rotation animation. */
public int getRotationAnimation() {
return mRotationAnimation;
}
/** @hide */ /** @hide */
@Override @Override
public void writeToParcel(@NonNull Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -487,6 +519,7 @@ public final class TransitionInfo implements Parcelable {
dest.writeTypedObject(mTaskInfo, flags); dest.writeTypedObject(mTaskInfo, flags);
dest.writeInt(mStartRotation); dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation); dest.writeInt(mEndRotation);
dest.writeInt(mRotationAnimation);
} }
@NonNull @NonNull
@@ -514,7 +547,7 @@ public final class TransitionInfo implements Parcelable {
return "{" + mContainer + "(" + mParent + ") leash=" + mLeash return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb=" + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
+ mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r=" + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
+ mStartRotation + "->" + mEndRotation + "}"; + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}";
} }
} }

View File

@@ -82,6 +82,9 @@ public class DisplayLayout {
private boolean mHasNavigationBar = false; private boolean mHasNavigationBar = false;
private boolean mHasStatusBar = false; private boolean mHasStatusBar = false;
private int mNavBarFrameHeight = 0; private int mNavBarFrameHeight = 0;
private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
private boolean mNavigationBarCanMove = false;
private boolean mReverseDefaultRotation = false;
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@@ -98,6 +101,10 @@ public class DisplayLayout {
&& Objects.equals(mStableInsets, other.mStableInsets) && Objects.equals(mStableInsets, other.mStableInsets)
&& mHasNavigationBar == other.mHasNavigationBar && mHasNavigationBar == other.mHasNavigationBar
&& mHasStatusBar == other.mHasStatusBar && mHasStatusBar == other.mHasStatusBar
&& mAllowSeamlessRotationDespiteNavBarMoving
== other.mAllowSeamlessRotationDespiteNavBarMoving
&& mNavigationBarCanMove == other.mNavigationBarCanMove
&& mReverseDefaultRotation == other.mReverseDefaultRotation
&& mNavBarFrameHeight == other.mNavBarFrameHeight; && mNavBarFrameHeight == other.mNavBarFrameHeight;
} }
@@ -105,7 +112,8 @@ public class DisplayLayout {
public int hashCode() { public int hashCode() {
return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi, return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar, mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
mNavBarFrameHeight); mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
mNavigationBarCanMove, mReverseDefaultRotation);
} }
/** /**
@@ -150,6 +158,9 @@ public class DisplayLayout {
mDensityDpi = dl.mDensityDpi; mDensityDpi = dl.mDensityDpi;
mHasNavigationBar = dl.mHasNavigationBar; mHasNavigationBar = dl.mHasNavigationBar;
mHasStatusBar = dl.mHasStatusBar; mHasStatusBar = dl.mHasStatusBar;
mAllowSeamlessRotationDespiteNavBarMoving = dl.mAllowSeamlessRotationDespiteNavBarMoving;
mNavigationBarCanMove = dl.mNavigationBarCanMove;
mReverseDefaultRotation = dl.mReverseDefaultRotation;
mNavBarFrameHeight = dl.mNavBarFrameHeight; mNavBarFrameHeight = dl.mNavBarFrameHeight;
mNonDecorInsets.set(dl.mNonDecorInsets); mNonDecorInsets.set(dl.mNonDecorInsets);
mStableInsets.set(dl.mStableInsets); mStableInsets.set(dl.mStableInsets);
@@ -165,6 +176,10 @@ public class DisplayLayout {
mDensityDpi = info.logicalDensityDpi; mDensityDpi = info.logicalDensityDpi;
mHasNavigationBar = hasNavigationBar; mHasNavigationBar = hasNavigationBar;
mHasStatusBar = hasStatusBar; 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); recalcInsets(res);
} }
@@ -249,6 +264,28 @@ public class DisplayLayout {
return mNavBarFrameHeight; 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 */ /** Gets the orientation of this layout */
public int getOrientation() { public int getOrientation() {
return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;

View File

@@ -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_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; 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_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; 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_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT; 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_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 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_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -68,9 +74,12 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.AttributeCache; import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation; import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog; 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.ShellExecutor;
import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup; 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); SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
private final TransactionPool mTransactionPool; private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
private final Context mContext; private final Context mContext;
private final ShellExecutor mMainExecutor; private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor; private final ShellExecutor mAnimExecutor;
@@ -114,8 +124,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private ScreenRotationAnimation mRotationAnimation; private ScreenRotationAnimation mRotationAnimation;
DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context, DefaultTransitionHandler(@NonNull DisplayController displayController,
@NonNull TransactionPool transactionPool, Context context,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mDisplayController = displayController;
mTransactionPool = transactionPool; mTransactionPool = transactionPool;
mContext = context; mContext = context;
mMainExecutor = mainExecutor; mMainExecutor = mainExecutor;
@@ -126,6 +138,110 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
AttributeCache.init(context); 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 @Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction startTransaction,
@@ -168,11 +284,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE
&& (change.getEndRotation() != change.getStartRotation()) && (change.getEndRotation() != change.getStartRotation())
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) { && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession, boolean isSeamless = isRotationSeamless(info, mDisplayController);
mTransactionPool, startTransaction, change, info.getRootLeash()); final int anim = getRotationAnimation(info);
mRotationAnimation.startAnimation(animations, onAnimFinish, if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor); mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
continue; mTransactionPool, startTransaction, change, info.getRootLeash());
mRotationAnimation.startAnimation(animations, onAnimFinish,
mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
continue;
}
} }
if (change.getMode() == TRANSIT_CHANGE) { if (change.getMode() == TRANSIT_CHANGE) {

View File

@@ -54,6 +54,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer; 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.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.TransactionPool;
@@ -113,15 +114,16 @@ public class Transitions implements RemoteCallable<Transitions> {
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>(); private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull Context context, @NonNull ShellExecutor mainExecutor, @NonNull DisplayController displayController, @NonNull Context context,
@NonNull ShellExecutor animExecutor) { @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer; mOrganizer = organizer;
mContext = context; mContext = context;
mMainExecutor = mainExecutor; mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor; mAnimExecutor = animExecutor;
mPlayerImpl = new TransitionPlayerImpl(); mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one. // 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. // Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor); mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler); mHandlers.add(mRemoteTransitionHandler);

View File

@@ -20,12 +20,18 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
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_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT; 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_TRANSLUCENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -49,6 +55,9 @@ import android.content.Context;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.WindowManager; import android.view.WindowManager;
import android.window.IRemoteTransition; import android.window.IRemoteTransition;
@@ -66,12 +75,15 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.TransactionPool;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList; import java.util.ArrayList;
@@ -98,8 +110,7 @@ public class ShellTransitionTests {
@Test @Test
public void testBasicTransitionFlow() { public void testBasicTransitionFlow() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken = new Binder(); IBinder transitToken = new Binder();
@@ -118,8 +129,7 @@ public class ShellTransitionTests {
@Test @Test
public void testNonDefaultHandler() { public void testNonDefaultHandler() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
@@ -202,8 +212,7 @@ public class ShellTransitionTests {
@Test @Test
public void testRequestRemoteTransition() { public void testRequestRemoteTransition() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false}; final boolean[] remoteCalled = new boolean[]{false};
@@ -297,8 +306,7 @@ public class ShellTransitionTests {
@Test @Test
public void testRegisteredRemoteTransition() { public void testRegisteredRemoteTransition() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false}; final boolean[] remoteCalled = new boolean[]{false};
@@ -343,8 +351,7 @@ public class ShellTransitionTests {
@Test @Test
public void testOneShotRemoteHandler() { public void testOneShotRemoteHandler() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false}; final boolean[] remoteCalled = new boolean[]{false};
@@ -390,8 +397,7 @@ public class ShellTransitionTests {
@Test @Test
public void testTransitionQueueing() { public void testTransitionQueueing() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken1 = new Binder(); IBinder transitToken1 = new Binder();
@@ -431,8 +437,7 @@ public class ShellTransitionTests {
@Test @Test
public void testTransitionMerging() { public void testTransitionMerging() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, Transitions transitions = createTestTransitions();
mMainExecutor, mAnimExecutor);
mDefaultHandler.setSimulateMerge(true); mDefaultHandler.setSimulateMerge(true);
transitions.replaceDefaultHandlerForTest(mDefaultHandler); transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -468,6 +473,63 @@ public class ShellTransitionTests {
assertEquals(0, mDefaultHandler.activeCount()); assertEquals(0, mDefaultHandler.activeCount());
} }
@Test
public void testShouldRotateSeamlessly() throws Exception {
final RunningTaskInfo taskInfo =
createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final RunningTaskInfo taskInfoPip =
createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
final DisplayController displays = createTestDisplayController();
final @Surface.Rotation int upsideDown = displays
.getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
.build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
.build();
assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
// Seamless if all tasks are seamless
final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
.build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.build();
assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
// Not seamless if there is PiP (or any other non-seamless task)
final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
.build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
.setRotate().build())
.build();
assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
// Not seamless if one of rotations is upside-down
final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
.setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
.build();
assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
// Not seamless if system alert windows
final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.build();
assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
}
class TransitionInfoBuilder { class TransitionInfoBuilder {
final TransitionInfo mInfo; final TransitionInfo mInfo;
@@ -490,11 +552,53 @@ public class ShellTransitionTests {
return addChange(mode, null /* taskInfo */); return addChange(mode, null /* taskInfo */);
} }
TransitionInfoBuilder addChange(TransitionInfo.Change change) {
mInfo.addChange(change);
return this;
}
TransitionInfo build() { TransitionInfo build() {
return mInfo; return mInfo;
} }
} }
class ChangeBuilder {
final TransitionInfo.Change mChange;
ChangeBuilder(@WindowManager.TransitionType int mode) {
mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
mChange.setMode(mode);
}
ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
mChange.setFlags(flags);
return this;
}
ChangeBuilder setTask(RunningTaskInfo taskInfo) {
mChange.setTaskInfo(taskInfo);
return this;
}
ChangeBuilder setRotate(int anim) {
return setRotate(Surface.ROTATION_90, anim);
}
ChangeBuilder setRotate() {
return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
}
ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
mChange.setRotation(Surface.ROTATION_0, target);
mChange.setRotationAnimation(anim);
return this;
}
TransitionInfo.Change build() {
return mChange;
}
}
class TestTransitionHandler implements Transitions.TransitionHandler { class TestTransitionHandler implements Transitions.TransitionHandler {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>(); ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>(); final ArrayList<IBinder> mMerged = new ArrayList<>();
@@ -566,4 +670,45 @@ public class ShellTransitionTests {
return taskInfo; return taskInfo;
} }
private DisplayController createTestDisplayController() {
IWindowManager mockWM = mock(IWindowManager.class);
final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
try {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
displayListener[0] = invocation.getArgument(0);
return null;
}
}).when(mockWM).registerDisplayWindowListener(any());
} catch (RemoteException e) {
// No remote stuff happening, so this can't be hit
}
DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
try {
displayListener[0].onDisplayAdded(DEFAULT_DISPLAY);
mMainExecutor.flushAll();
} catch (RemoteException e) {
// Again, no remote stuff
}
return out;
}
private Transitions createTestTransitions() {
return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
mContext, mMainExecutor, mAnimExecutor);
}
//
// private class TestDisplayController extends DisplayController {
// private final DisplayLayout mTestDisplayLayout;
// TestDisplayController() {
// super(mContext, mock(IWindowManager.class), mMainExecutor);
// mTestDisplayLayout = new DisplayLayout();
// mTestDisplayLayout.
// }
//
// @Override
// DisplayLayout
// }
} }

View File

@@ -328,9 +328,11 @@ public abstract class WMShellBaseModule {
@WMSingleton @WMSingleton
@Provides @Provides
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool, static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
Context context, @ShellMainThread ShellExecutor mainExecutor, DisplayController displayController, Context context,
@ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor) { @ShellAnimationThread ShellExecutor animExecutor) {
return new Transitions(organizer, pool, context, mainExecutor, animExecutor); return new Transitions(organizer, pool, displayController, context, mainExecutor,
animExecutor);
} }
// //

View File

@@ -20,6 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.INVALID_DISPLAY;
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_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
@@ -36,6 +38,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString; import static android.view.WindowManager.transitTypeToString;
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_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -1066,6 +1069,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
task.fillTaskInfo(tinfo); task.fillTaskInfo(tinfo);
change.setTaskInfo(tinfo); change.setTaskInfo(tinfo);
change.setRotationAnimation(getTaskRotationAnimation(task));
} }
out.addChange(change); out.addChange(change);
} }
@@ -1073,6 +1077,23 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return out; return out;
} }
private static int getTaskRotationAnimation(@NonNull Task task) {
final ActivityRecord top = task.getTopVisibleActivity();
if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
final WindowState mainWin = top.findMainWindow(false);
if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
int anim = mainWin.getRotationAnimationHint();
if (anim >= 0) return anim;
anim = mainWin.getAttrs().rotationAnimation;
if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
|| !top.matchParentBounds()) {
// At the moment, we only support seamless rotation if there is only one window showing.
return ROTATION_ANIMATION_UNSPECIFIED;
}
return mainWin.getAttrs().rotationAnimation;
}
boolean getLegacyIsReady() { boolean getLegacyIsReady() {
return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId); return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId);
} }
@@ -1166,6 +1187,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final DisplayContent dc = wc.asDisplayContent(); final DisplayContent dc = wc.asDisplayContent();
if (dc != null) { if (dc != null) {
flags |= FLAG_IS_DISPLAY; flags |= FLAG_IS_DISPLAY;
if (dc.hasAlertWindowSurfaces()) {
flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
}
} }
if (isWallpaper(wc)) { if (isWallpaper(wc)) {
flags |= FLAG_IS_WALLPAPER; flags |= FLAG_IS_WALLPAPER;