Merge "Differentiate seamless rotations in shell transitions" into sc-v2-dev
This commit is contained in:
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,11 +284,15 @@ 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) {
|
||||
mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
|
||||
mTransactionPool, startTransaction, change, info.getRootLeash());
|
||||
mRotationAnimation.startAnimation(animations, onAnimFinish,
|
||||
mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
|
||||
continue;
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.WINDOWING_MODE_FULLSCREEN;
|
||||
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_CLOSE;
|
||||
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
|
||||
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.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
|
||||
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
|
||||
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
|
||||
|
||||
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.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.view.IDisplayWindowListener;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManager;
|
||||
import android.window.IRemoteTransition;
|
||||
@@ -66,12 +75,15 @@ import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
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.TransactionPool;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -98,8 +110,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testBasicTransitionFlow() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
IBinder transitToken = new Binder();
|
||||
@@ -118,8 +129,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testNonDefaultHandler() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
|
||||
@@ -202,8 +212,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testRequestRemoteTransition() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
final boolean[] remoteCalled = new boolean[]{false};
|
||||
@@ -297,8 +306,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testRegisteredRemoteTransition() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
final boolean[] remoteCalled = new boolean[]{false};
|
||||
@@ -343,8 +351,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testOneShotRemoteHandler() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
final boolean[] remoteCalled = new boolean[]{false};
|
||||
@@ -390,8 +397,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testTransitionQueueing() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
IBinder transitToken1 = new Binder();
|
||||
@@ -431,8 +437,7 @@ public class ShellTransitionTests {
|
||||
|
||||
@Test
|
||||
public void testTransitionMerging() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
|
||||
mMainExecutor, mAnimExecutor);
|
||||
Transitions transitions = createTestTransitions();
|
||||
mDefaultHandler.setSimulateMerge(true);
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
@@ -468,6 +473,63 @@ public class ShellTransitionTests {
|
||||
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 {
|
||||
final TransitionInfo mInfo;
|
||||
|
||||
@@ -490,11 +552,53 @@ public class ShellTransitionTests {
|
||||
return addChange(mode, null /* taskInfo */);
|
||||
}
|
||||
|
||||
TransitionInfoBuilder addChange(TransitionInfo.Change change) {
|
||||
mInfo.addChange(change);
|
||||
return this;
|
||||
}
|
||||
|
||||
TransitionInfo build() {
|
||||
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 {
|
||||
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
|
||||
final ArrayList<IBinder> mMerged = new ArrayList<>();
|
||||
@@ -566,4 +670,45 @@ public class ShellTransitionTests {
|
||||
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
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -328,9 +328,11 @@ public abstract class WMShellBaseModule {
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
|
||||
Context context, @ShellMainThread ShellExecutor mainExecutor,
|
||||
DisplayController displayController, Context context,
|
||||
@ShellMainThread ShellExecutor mainExecutor,
|
||||
@ShellAnimationThread ShellExecutor animExecutor) {
|
||||
return new Transitions(organizer, pool, context, mainExecutor, animExecutor);
|
||||
return new Transitions(organizer, pool, displayController, context, mainExecutor,
|
||||
animExecutor);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -20,6 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
|
||||
import static android.view.Display.DEFAULT_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_CLOSE;
|
||||
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.TransitionType;
|
||||
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_VOICE_INTERACTION;
|
||||
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();
|
||||
task.fillTaskInfo(tinfo);
|
||||
change.setTaskInfo(tinfo);
|
||||
change.setRotationAnimation(getTaskRotationAnimation(task));
|
||||
}
|
||||
out.addChange(change);
|
||||
}
|
||||
@@ -1073,6 +1077,23 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
|
||||
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() {
|
||||
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();
|
||||
if (dc != null) {
|
||||
flags |= FLAG_IS_DISPLAY;
|
||||
if (dc.hasAlertWindowSurfaces()) {
|
||||
flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
|
||||
}
|
||||
}
|
||||
if (isWallpaper(wc)) {
|
||||
flags |= FLAG_IS_WALLPAPER;
|
||||
|
||||
Reference in New Issue
Block a user