Only play ActivityEmbedding animation if there is AE split
Same as the legacy app transition, we want to play the app customized animation if there is no AE split. We don't support app customized animation when there is AE split yet to make the animation consistant. Bug: 207070762 Test: atest WMShellUnitTests:ActivityEmbeddingControllerTests Test: atest WmTests:TransitionTests Change-Id: I68348cd650b9dbc8c39ef8d22a171d128e196882
This commit is contained in:
@@ -113,8 +113,8 @@ public final class TransitionInfo implements Parcelable {
|
||||
/** The container is in a Task with embedded activity. */
|
||||
public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9;
|
||||
|
||||
/** 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 << 10;
|
||||
/** The container fills its parent Task before and after the transition. */
|
||||
public static final int FLAG_FILLS_TASK = 1 << 10;
|
||||
|
||||
/** The container is going to show IME on its task after the transition. */
|
||||
public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
|
||||
@@ -125,6 +125,9 @@ public final class TransitionInfo implements Parcelable {
|
||||
/** The container attaches work profile thumbnail for cross profile animation. */
|
||||
public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
|
||||
|
||||
/** 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 << 14;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "FLAG_" }, value = {
|
||||
FLAG_NONE,
|
||||
@@ -138,8 +141,11 @@ public final class TransitionInfo implements Parcelable {
|
||||
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
|
||||
FLAG_IS_INPUT_METHOD,
|
||||
FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY,
|
||||
FLAG_FIRST_CUSTOM,
|
||||
FLAG_WILL_IME_SHOWN
|
||||
FLAG_FILLS_TASK,
|
||||
FLAG_WILL_IME_SHOWN,
|
||||
FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
|
||||
FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
|
||||
FLAG_FIRST_CUSTOM
|
||||
})
|
||||
public @interface ChangeFlags {}
|
||||
|
||||
@@ -342,6 +348,9 @@ public final class TransitionInfo implements Parcelable {
|
||||
if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) {
|
||||
sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY");
|
||||
}
|
||||
if ((flags & FLAG_FILLS_TASK) != 0) {
|
||||
sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK");
|
||||
}
|
||||
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
|
||||
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.wm.shell.activityembedding;
|
||||
|
||||
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
|
||||
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -84,12 +85,23 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
|
||||
@NonNull SurfaceControl.Transaction startTransaction,
|
||||
@NonNull SurfaceControl.Transaction finishTransaction,
|
||||
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
||||
// TODO(b/207070762) Handle AE animation as a part of other transitions.
|
||||
boolean containsEmbeddingSplit = false;
|
||||
for (TransitionInfo.Change change : info.getChanges()) {
|
||||
if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
|
||||
// Only animate the transition if all changes are in a Task with ActivityEmbedding.
|
||||
return false;
|
||||
}
|
||||
if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) {
|
||||
// Whether the Task contains any ActivityEmbedding split before or after the
|
||||
// transition.
|
||||
containsEmbeddingSplit = true;
|
||||
}
|
||||
}
|
||||
if (!containsEmbeddingSplit) {
|
||||
// Let the system to play the default animation if there is no ActivityEmbedding split
|
||||
// window. This allows to play the app customized animation when there is no embedding,
|
||||
// such as the device is in a folded state.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start ActivityEmbedding animation.
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.wm.shell.activityembedding;
|
||||
|
||||
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
|
||||
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -24,6 +27,8 @@ import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.NonNull;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.TransitionInfo;
|
||||
@@ -80,4 +85,23 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
|
||||
return new TransitionInfo.Change(mock(WindowContainerToken.class),
|
||||
mock(SurfaceControl.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock {@link TransitionInfo.Change} with
|
||||
* {@link TransitionInfo#FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY} flag.
|
||||
*/
|
||||
static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds,
|
||||
@NonNull Rect endBounds, @NonNull Rect taskBounds) {
|
||||
final TransitionInfo.Change change = createChange();
|
||||
change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
|
||||
change.setStartAbsBounds(startBounds);
|
||||
change.setEndAbsBounds(endBounds);
|
||||
if (taskBounds.width() == startBounds.width()
|
||||
&& taskBounds.height() == startBounds.height()
|
||||
&& taskBounds.width() == endBounds.width()
|
||||
&& taskBounds.height() == endBounds.height()) {
|
||||
change.setFlags(FLAG_FILLS_TASK);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.wm.shell.activityembedding;
|
||||
|
||||
import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
|
||||
|
||||
@@ -29,6 +28,7 @@ import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.window.TransitionInfo;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -48,6 +48,10 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase {
|
||||
|
||||
private static final Rect TASK_BOUNDS = new Rect(0, 0, 1000, 500);
|
||||
private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500);
|
||||
private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
super.setUp();
|
||||
@@ -77,13 +81,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
|
||||
@Test
|
||||
public void testStartAnimation_containsNonActivityEmbeddingChange() {
|
||||
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
|
||||
final TransitionInfo.Change embeddingChange = createChange();
|
||||
embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
|
||||
final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
|
||||
EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
|
||||
final TransitionInfo.Change nonEmbeddingChange = createChange();
|
||||
info.addChange(embeddingChange);
|
||||
info.addChange(nonEmbeddingChange);
|
||||
|
||||
// No-op
|
||||
// No-op because it contains non-embedded change.
|
||||
assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction, mFinishCallback));
|
||||
verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any());
|
||||
@@ -93,13 +97,65 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAnimation_onlyActivityEmbeddingChange() {
|
||||
public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() {
|
||||
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
|
||||
final TransitionInfo.Change embeddingChange = createChange();
|
||||
embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
|
||||
final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS,
|
||||
TASK_BOUNDS);
|
||||
info.addChange(embeddingChange);
|
||||
|
||||
// No-op
|
||||
// No-op because it only contains embedded change that fills the Task. We will let the
|
||||
// default handler to animate such transition.
|
||||
assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction, mFinishCallback));
|
||||
verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any());
|
||||
verifyNoMoreInteractions(mStartTransaction);
|
||||
verifyNoMoreInteractions(mFinishTransaction);
|
||||
verifyNoMoreInteractions(mFinishCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAnimation_containsActivityEmbeddingSplitChange() {
|
||||
// Change that occupies only part of the Task.
|
||||
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
|
||||
final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
|
||||
EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
|
||||
info.addChange(embeddingChange);
|
||||
|
||||
// ActivityEmbeddingController will handle such transition.
|
||||
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction, mFinishCallback));
|
||||
verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction);
|
||||
verify(mStartTransaction).apply();
|
||||
verifyNoMoreInteractions(mFinishTransaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() {
|
||||
// Change that is entering ActivityEmbedding split.
|
||||
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
|
||||
final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS,
|
||||
EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
|
||||
info.addChange(embeddingChange);
|
||||
|
||||
// ActivityEmbeddingController will handle such transition.
|
||||
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction, mFinishCallback));
|
||||
verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction);
|
||||
verify(mStartTransaction).apply();
|
||||
verifyNoMoreInteractions(mFinishTransaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() {
|
||||
// Change that is exiting ActivityEmbedding split.
|
||||
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
|
||||
final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS,
|
||||
TASK_BOUNDS, TASK_BOUNDS);
|
||||
info.addChange(embeddingChange);
|
||||
|
||||
// ActivityEmbeddingController will handle such transition.
|
||||
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction, mFinishCallback));
|
||||
verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
|
||||
@@ -115,8 +171,8 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
|
||||
() -> mController.onAnimationFinished(mTransition));
|
||||
|
||||
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
|
||||
final TransitionInfo.Change embeddingChange = createChange();
|
||||
embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
|
||||
final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
|
||||
EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
|
||||
info.addChange(embeddingChange);
|
||||
mController.startAnimation(mTransition, info, mStartTransaction,
|
||||
mFinishTransaction, mFinishCallback);
|
||||
|
||||
@@ -46,6 +46,7 @@ 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_FILLS_TASK;
|
||||
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
|
||||
@@ -1888,10 +1889,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
|
||||
if (taskFragment != null && task == null) {
|
||||
parentTask = taskFragment.getTask();
|
||||
}
|
||||
if (parentTask != null
|
||||
&& parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
|
||||
// Whether this is in a Task with embedded activity.
|
||||
flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
if (parentTask != null) {
|
||||
if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
|
||||
// Whether this is in a Task with embedded activity.
|
||||
flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
}
|
||||
final Rect taskBounds = parentTask.getBounds();
|
||||
final Rect startBounds = mAbsoluteBounds;
|
||||
final Rect endBounds = wc.getBounds();
|
||||
if (taskBounds.width() == startBounds.width()
|
||||
&& taskBounds.height() == startBounds.height()
|
||||
&& taskBounds.width() == endBounds.width()
|
||||
&& taskBounds.height() == endBounds.height()) {
|
||||
// Whether the container fills the Task bounds before and after the transition.
|
||||
flags |= FLAG_FILLS_TASK;
|
||||
}
|
||||
}
|
||||
final DisplayContent dc = wc.asDisplayContent();
|
||||
if (dc != null) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.wm;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
|
||||
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
|
||||
@@ -31,6 +32,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
|
||||
import static android.view.WindowManager.TRANSIT_CLOSE;
|
||||
import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
|
||||
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
|
||||
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
|
||||
@@ -1107,6 +1109,51 @@ public class TransitionTests extends WindowTestsBase {
|
||||
assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlagFillsTask() {
|
||||
final Transition transition = createTestTransition(TRANSIT_OPEN);
|
||||
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
|
||||
final ArraySet<WindowContainer> participants = transition.mParticipants;
|
||||
|
||||
final Task task = createTask(mDisplayContent);
|
||||
// Set to multi-windowing mode in order to set bounds.
|
||||
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
|
||||
final Rect taskBounds = new Rect(0, 0, 500, 1000);
|
||||
task.setBounds(taskBounds);
|
||||
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
|
||||
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
|
||||
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
|
||||
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
|
||||
final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
|
||||
.setParentTask(task)
|
||||
.createActivityCount(1)
|
||||
.setOrganizer(organizer)
|
||||
.build();
|
||||
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
|
||||
// Start states.
|
||||
changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
|
||||
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
|
||||
false /* exChg */));
|
||||
changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
|
||||
// End states.
|
||||
nonEmbeddedActivity.mVisibleRequested = false;
|
||||
embeddedActivity.mVisibleRequested = true;
|
||||
embeddedTf.setBounds(new Rect(0, 0, 500, 500));
|
||||
|
||||
participants.add(nonEmbeddedActivity);
|
||||
participants.add(embeddedTf);
|
||||
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
|
||||
participants, changes);
|
||||
final TransitionInfo info = Transition.calculateTransitionInfo(
|
||||
transition.mType, 0 /* flags */, targets, changes, mMockT);
|
||||
|
||||
// The embedded with bounds overridden should not have the flag.
|
||||
assertEquals(2, info.getChanges().size());
|
||||
assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
|
||||
assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds());
|
||||
assertFalse(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncludeEmbeddedActivityReparent() {
|
||||
final Transition transition = createTestTransition(TRANSIT_OPEN);
|
||||
|
||||
Reference in New Issue
Block a user