Loading core/java/android/window/TransitionInfo.java +22 −0 Original line number Diff line number Diff line Loading @@ -663,6 +663,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mStartAbsBounds = new Rect(); private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); private final Point mEndParentSize = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; private boolean mAllowEnterPip; private int mStartDisplayId = INVALID_DISPLAY; Loading Loading @@ -697,6 +698,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.readFromParcel(in); mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); mEndParentSize.readFromParcel(in); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); mAllowEnterPip = in.readBoolean(); mStartDisplayId = in.readInt(); Loading @@ -721,6 +723,7 @@ public final class TransitionInfo implements Parcelable { out.mStartAbsBounds.set(mStartAbsBounds); out.mEndAbsBounds.set(mEndAbsBounds); out.mEndRelOffset.set(mEndRelOffset); out.mEndParentSize.set(mEndParentSize); out.mTaskInfo = mTaskInfo; out.mAllowEnterPip = mAllowEnterPip; out.mStartDisplayId = mStartDisplayId; Loading Loading @@ -780,6 +783,13 @@ public final class TransitionInfo implements Parcelable { mEndRelOffset.set(left, top); } /** * Sets the size of its parent container after the change. */ public void setEndParentSize(int width, int height) { mEndParentSize.set(width, height); } /** * Sets the taskinfo of this container if this is a task. WARNING: this takes the * reference, so don't modify it afterwards. Loading Loading @@ -916,6 +926,14 @@ public final class TransitionInfo implements Parcelable { return mEndRelOffset; } /** * Returns the size of parent container after the change. */ @NonNull public Point getEndParentSize() { return mEndParentSize; } /** @return the leash or surface to animate for this container */ @NonNull public SurfaceControl getLeash() { Loading Loading @@ -1003,6 +1021,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.writeToParcel(dest, flags); mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); mEndParentSize.writeToParcel(dest, flags); dest.writeTypedObject(mTaskInfo, flags); dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartDisplayId); Loading Loading @@ -1055,6 +1074,9 @@ public final class TransitionInfo implements Parcelable { if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) { sb.append(" eo="); sb.append(mEndRelOffset); } if (!mEndParentSize.equals(0, 0)) { sb.append(" epz=").append(mEndParentSize); } sb.append(" d="); if (mStartDisplayId != mEndDisplayId) { sb.append(mStartDisplayId).append("->"); Loading libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +59 −14 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_ import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; Loading @@ -45,6 +46,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.shared.TransitionUtil; Loading Loading @@ -398,7 +400,15 @@ class ActivityEmbeddingAnimationRunner { // This is because the TaskFragment surface/change won't contain the Activity's before its // reparent. Animation changeAnimation = null; Rect parentBounds = new Rect(); final Rect parentBounds = new Rect(); // We use a single boolean value to record the backdrop override because the override used // for overlay and we restrict to single overlay animation. We should fix the assumption // if we allow multiple overlay transitions. // The backdrop logic is mainly for animations of split animations. The backdrop should be // disabled if there is any open/close target in the same transition as the change target. // However, the overlay change animation usually contains one change target, and shows // backdrop unexpectedly. Boolean overrideShowBackdrop = null; for (TransitionInfo.Change change : info.getChanges()) { if (change.getMode() != TRANSIT_CHANGE || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { Loading @@ -421,17 +431,17 @@ class ActivityEmbeddingAnimationRunner { } } // The TaskFragment may be enter/exit split, so we take the union of both as the parent // size. parentBounds.union(boundsAnimationChange.getStartAbsBounds()); parentBounds.union(boundsAnimationChange.getEndAbsBounds()); if (boundsAnimationChange != change) { // Union the change starting bounds in case the activity is resized and reparented // to a TaskFragment. In that case, the TaskFragment may not cover the activity's // starting bounds. parentBounds.union(change.getStartAbsBounds()); final TransitionInfo.AnimationOptions options = boundsAnimationChange .getAnimationOptions(); if (options != null) { final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions( options, TRANSIT_CHANGE); if (overrideAnimation != null) { overrideShowBackdrop = overrideAnimation.getShowBackdrop(); } } calculateParentBounds(change, boundsAnimationChange, parentBounds); // There are two animations in the array. The first one is for the start leash // (snapshot), and the second one is for the end leash (TaskFragment). final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, Loading Loading @@ -466,7 +476,7 @@ class ActivityEmbeddingAnimationRunner { // If there is no corresponding open/close window with the change, we should show background // color to cover the empty part of the screen. boolean shouldShouldBackgroundColor = true; boolean shouldShowBackgroundColor = true; // Handle the other windows that don't have bounds change in the same transition. for (TransitionInfo.Change change : info.getChanges()) { if (handledChanges.contains(change)) { Loading @@ -483,16 +493,18 @@ class ActivityEmbeddingAnimationRunner { animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (TransitionUtil.isClosingType(change.getMode())) { animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); shouldShouldBackgroundColor = false; shouldShowBackgroundColor = false; } else { animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); shouldShouldBackgroundColor = false; shouldShowBackgroundColor = false; } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); } if (shouldShouldBackgroundColor && changeAnimation != null) { shouldShowBackgroundColor = overrideShowBackdrop != null ? overrideShowBackdrop : shouldShowBackgroundColor; if (shouldShowBackgroundColor && changeAnimation != null) { // Change animation may leave part of the screen empty. Show background color to cover // that. changeAnimation.setShowBackdrop(true); Loading @@ -501,6 +513,39 @@ class ActivityEmbeddingAnimationRunner { return adapters; } /** * Calculates parent bounds of the animation target by {@code change}. */ @VisibleForTesting static void calculateParentBounds(@NonNull TransitionInfo.Change change, @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) { if (Flags.activityEmbeddingOverlayPresentationFlag()) { final Point endParentSize = change.getEndParentSize(); if (endParentSize.equals(0, 0)) { return; } final Point endRelPosition = change.getEndRelOffset(); final Point endAbsPosition = new Point(change.getEndAbsBounds().left, change.getEndAbsBounds().top); final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x, endAbsPosition.y - endRelPosition.y); outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y, parentEndAbsPosition.x + endParentSize.x, parentEndAbsPosition.y + endParentSize.y); } else { // The TaskFragment may be enter/exit split, so we take the union of both as // the parent size. outParentBounds.union(boundsAnimationChange.getStartAbsBounds()); outParentBounds.union(boundsAnimationChange.getEndAbsBounds()); if (boundsAnimationChange != change) { // Union the change starting bounds in case the activity is resized and // reparented to a TaskFragment. In that case, the TaskFragment may not cover // the activity's starting bounds. outParentBounds.union(change.getStartAbsBounds()); } } } /** * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. * The screenshot leash should be attached to the {@code animationChange} surface which we will Loading libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +30 −4 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.wm.shell.activityembedding; import static android.app.ActivityOptions.ANIM_CUSTOM; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; Loading @@ -27,6 +29,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.util.Log; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; Loading Loading @@ -203,7 +207,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); final Animation customAnimation = loadCustomAnimation(info, change, isEnter); final Animation customAnimation = loadCustomAnimation(info, change); final Animation animation; if (customAnimation != null) { animation = customAnimation; Loading @@ -230,7 +234,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); final Animation customAnimation = loadCustomAnimation(info, change, isEnter); final Animation customAnimation = loadCustomAnimation(info, change); final Animation animation; if (customAnimation != null) { animation = customAnimation; Loading Loading @@ -263,18 +267,40 @@ class ActivityEmbeddingAnimationSpec { @Nullable private Animation loadCustomAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, boolean isEnter) { @NonNull TransitionInfo.Change change) { final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { options = change.getAnimationOptions(); } else { options = info.getAnimationOptions(); } return loadCustomAnimationFromOptions(options, change.getMode()); } @Nullable Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options, @WindowManager.TransitionType int mode) { if (options == null || options.getType() != ANIM_CUSTOM) { return null; } final int resId; if (TransitionUtil.isOpeningType(mode)) { resId = options.getEnterResId(); } else if (TransitionUtil.isClosingType(mode)) { resId = options.getExitResId(); } else if (mode == TRANSIT_CHANGE) { resId = options.getChangeResId(); } else { Log.w(TAG, "Unknown transit type:" + mode); resId = DEFAULT_ANIMATION_RESOURCES_ID; } // Use the default animation if the resources ID is not specified. if (resId == DEFAULT_ANIMATION_RESOURCES_ID) { return null; } final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), isEnter ? options.getEnterResId() : options.getExitResId()); resId); if (anim != null) { return anim; } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +131 −2 Original line number Diff line number Diff line Loading @@ -20,9 +20,11 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds; import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; Loading @@ -32,6 +34,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.animation.Animator; import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; Loading Loading @@ -130,7 +135,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, Loading @@ -148,7 +153,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, Loading @@ -161,4 +166,128 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim // An invalid custom animation is equivalent to jump-cut. assertEquals(0, animator.getDuration()); } @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test public void testCalculateParentBounds_flagDisabled() { final Rect parentBounds = new Rect(0, 0, 2000, 2000); final Rect primaryBounds = new Rect(); final Rect secondaryBounds = new Rect(); parentBounds.splitVertically(primaryBounds, secondaryBounds); final TransitionInfo.Change change = createChange(0 /* flags */); change.setStartAbsBounds(secondaryBounds); final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */); boundsAnimationChange.setStartAbsBounds(primaryBounds); boundsAnimationChange.setEndAbsBounds(primaryBounds); final Rect actualParentBounds = new Rect(); calculateParentBounds(change, boundsAnimationChange, actualParentBounds); assertEquals(parentBounds, actualParentBounds); actualParentBounds.setEmpty(); boundsAnimationChange.setStartAbsBounds(secondaryBounds); boundsAnimationChange.setEndAbsBounds(primaryBounds); calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds); assertEquals(parentBounds, actualParentBounds); } // TODO(b/243518738): Rewrite with TestParameter @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test public void testCalculateParentBounds_flagEnabled() { TransitionInfo.Change change; final TransitionInfo.Change stubChange = createChange(0 /* flags */); final Rect actualParentBounds = new Rect(); Rect parentBounds = new Rect(0, 0, 2000, 2000); Rect endAbsBounds = new Rect(0, 0, 2000, 2000); change = prepareChangeForParentBoundsCalculationTest( new Point(0, 0) /* endRelOffset */, endAbsBounds, new Point() /* endParentSize */ ); calculateParentBounds(change, stubChange, actualParentBounds); assertTrue("Parent bounds must be empty because end parent size is not set.", actualParentBounds.isEmpty()); String testString = "Parent start with (0, 0)"; change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Container not start with (0, 0)"; parentBounds = new Rect(0, 0, 2000, 2000); endAbsBounds = new Rect(1000, 500, 2000, 1500); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Parent container on the right"; parentBounds = new Rect(1000, 0, 2000, 2000); endAbsBounds = new Rect(1000, 500, 1500, 1500); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Parent container on the bottom"; parentBounds = new Rect(0, 1000, 2000, 2000); endAbsBounds = new Rect(500, 1500, 1500, 2000); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Parent container in the middle"; parentBounds = new Rect(500, 500, 1500, 1500); endAbsBounds = new Rect(1000, 500, 1500, 1000); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); } @NonNull private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest( @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) { final TransitionInfo.Change change = createChange(0 /* flags */); change.setEndRelOffset(endRelOffset.x, endRelOffset.y); change.setEndAbsBounds(endAbsBounds); change.setEndParentSize(endParentSize.x, endParentSize.y); return change; } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +19 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_NONE; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; Loading @@ -31,6 +32,7 @@ import android.annotation.NonNull; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.WindowContainerToken; Loading Loading @@ -82,11 +84,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { spyOn(mFinishCallback); } /** Creates a mock {@link TransitionInfo.Change}. */ /** * Creates a mock {@link TransitionInfo.Change}. * * @param flags the {@link TransitionInfo.ChangeFlags} of the change */ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) { return createChange(flags, TRANSIT_NONE); } /** * Creates a mock {@link TransitionInfo.Change}. * * @param flags the {@link TransitionInfo.ChangeFlags} of the change * @param mode the transition mode of the change */ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags, @WindowManager.TransitionType int mode) { TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); c.setFlags(flags); c.setMode(mode); return c; } Loading Loading
core/java/android/window/TransitionInfo.java +22 −0 Original line number Diff line number Diff line Loading @@ -663,6 +663,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mStartAbsBounds = new Rect(); private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); private final Point mEndParentSize = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; private boolean mAllowEnterPip; private int mStartDisplayId = INVALID_DISPLAY; Loading Loading @@ -697,6 +698,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.readFromParcel(in); mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); mEndParentSize.readFromParcel(in); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); mAllowEnterPip = in.readBoolean(); mStartDisplayId = in.readInt(); Loading @@ -721,6 +723,7 @@ public final class TransitionInfo implements Parcelable { out.mStartAbsBounds.set(mStartAbsBounds); out.mEndAbsBounds.set(mEndAbsBounds); out.mEndRelOffset.set(mEndRelOffset); out.mEndParentSize.set(mEndParentSize); out.mTaskInfo = mTaskInfo; out.mAllowEnterPip = mAllowEnterPip; out.mStartDisplayId = mStartDisplayId; Loading Loading @@ -780,6 +783,13 @@ public final class TransitionInfo implements Parcelable { mEndRelOffset.set(left, top); } /** * Sets the size of its parent container after the change. */ public void setEndParentSize(int width, int height) { mEndParentSize.set(width, height); } /** * Sets the taskinfo of this container if this is a task. WARNING: this takes the * reference, so don't modify it afterwards. Loading Loading @@ -916,6 +926,14 @@ public final class TransitionInfo implements Parcelable { return mEndRelOffset; } /** * Returns the size of parent container after the change. */ @NonNull public Point getEndParentSize() { return mEndParentSize; } /** @return the leash or surface to animate for this container */ @NonNull public SurfaceControl getLeash() { Loading Loading @@ -1003,6 +1021,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.writeToParcel(dest, flags); mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); mEndParentSize.writeToParcel(dest, flags); dest.writeTypedObject(mTaskInfo, flags); dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartDisplayId); Loading Loading @@ -1055,6 +1074,9 @@ public final class TransitionInfo implements Parcelable { if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) { sb.append(" eo="); sb.append(mEndRelOffset); } if (!mEndParentSize.equals(0, 0)) { sb.append(" epz=").append(mEndParentSize); } sb.append(" d="); if (mStartDisplayId != mEndDisplayId) { sb.append(mStartDisplayId).append("->"); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +59 −14 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_ import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; Loading @@ -45,6 +46,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.shared.TransitionUtil; Loading Loading @@ -398,7 +400,15 @@ class ActivityEmbeddingAnimationRunner { // This is because the TaskFragment surface/change won't contain the Activity's before its // reparent. Animation changeAnimation = null; Rect parentBounds = new Rect(); final Rect parentBounds = new Rect(); // We use a single boolean value to record the backdrop override because the override used // for overlay and we restrict to single overlay animation. We should fix the assumption // if we allow multiple overlay transitions. // The backdrop logic is mainly for animations of split animations. The backdrop should be // disabled if there is any open/close target in the same transition as the change target. // However, the overlay change animation usually contains one change target, and shows // backdrop unexpectedly. Boolean overrideShowBackdrop = null; for (TransitionInfo.Change change : info.getChanges()) { if (change.getMode() != TRANSIT_CHANGE || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { Loading @@ -421,17 +431,17 @@ class ActivityEmbeddingAnimationRunner { } } // The TaskFragment may be enter/exit split, so we take the union of both as the parent // size. parentBounds.union(boundsAnimationChange.getStartAbsBounds()); parentBounds.union(boundsAnimationChange.getEndAbsBounds()); if (boundsAnimationChange != change) { // Union the change starting bounds in case the activity is resized and reparented // to a TaskFragment. In that case, the TaskFragment may not cover the activity's // starting bounds. parentBounds.union(change.getStartAbsBounds()); final TransitionInfo.AnimationOptions options = boundsAnimationChange .getAnimationOptions(); if (options != null) { final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions( options, TRANSIT_CHANGE); if (overrideAnimation != null) { overrideShowBackdrop = overrideAnimation.getShowBackdrop(); } } calculateParentBounds(change, boundsAnimationChange, parentBounds); // There are two animations in the array. The first one is for the start leash // (snapshot), and the second one is for the end leash (TaskFragment). final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, Loading Loading @@ -466,7 +476,7 @@ class ActivityEmbeddingAnimationRunner { // If there is no corresponding open/close window with the change, we should show background // color to cover the empty part of the screen. boolean shouldShouldBackgroundColor = true; boolean shouldShowBackgroundColor = true; // Handle the other windows that don't have bounds change in the same transition. for (TransitionInfo.Change change : info.getChanges()) { if (handledChanges.contains(change)) { Loading @@ -483,16 +493,18 @@ class ActivityEmbeddingAnimationRunner { animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (TransitionUtil.isClosingType(change.getMode())) { animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); shouldShouldBackgroundColor = false; shouldShowBackgroundColor = false; } else { animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); shouldShouldBackgroundColor = false; shouldShowBackgroundColor = false; } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); } if (shouldShouldBackgroundColor && changeAnimation != null) { shouldShowBackgroundColor = overrideShowBackdrop != null ? overrideShowBackdrop : shouldShowBackgroundColor; if (shouldShowBackgroundColor && changeAnimation != null) { // Change animation may leave part of the screen empty. Show background color to cover // that. changeAnimation.setShowBackdrop(true); Loading @@ -501,6 +513,39 @@ class ActivityEmbeddingAnimationRunner { return adapters; } /** * Calculates parent bounds of the animation target by {@code change}. */ @VisibleForTesting static void calculateParentBounds(@NonNull TransitionInfo.Change change, @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) { if (Flags.activityEmbeddingOverlayPresentationFlag()) { final Point endParentSize = change.getEndParentSize(); if (endParentSize.equals(0, 0)) { return; } final Point endRelPosition = change.getEndRelOffset(); final Point endAbsPosition = new Point(change.getEndAbsBounds().left, change.getEndAbsBounds().top); final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x, endAbsPosition.y - endRelPosition.y); outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y, parentEndAbsPosition.x + endParentSize.x, parentEndAbsPosition.y + endParentSize.y); } else { // The TaskFragment may be enter/exit split, so we take the union of both as // the parent size. outParentBounds.union(boundsAnimationChange.getStartAbsBounds()); outParentBounds.union(boundsAnimationChange.getEndAbsBounds()); if (boundsAnimationChange != change) { // Union the change starting bounds in case the activity is resized and // reparented to a TaskFragment. In that case, the TaskFragment may not cover // the activity's starting bounds. outParentBounds.union(change.getStartAbsBounds()); } } } /** * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. * The screenshot leash should be attached to the {@code animationChange} surface which we will Loading
libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +30 −4 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.wm.shell.activityembedding; import static android.app.ActivityOptions.ANIM_CUSTOM; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; Loading @@ -27,6 +29,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.util.Log; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; Loading Loading @@ -203,7 +207,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); final Animation customAnimation = loadCustomAnimation(info, change, isEnter); final Animation customAnimation = loadCustomAnimation(info, change); final Animation animation; if (customAnimation != null) { animation = customAnimation; Loading @@ -230,7 +234,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); final Animation customAnimation = loadCustomAnimation(info, change, isEnter); final Animation customAnimation = loadCustomAnimation(info, change); final Animation animation; if (customAnimation != null) { animation = customAnimation; Loading Loading @@ -263,18 +267,40 @@ class ActivityEmbeddingAnimationSpec { @Nullable private Animation loadCustomAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, boolean isEnter) { @NonNull TransitionInfo.Change change) { final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { options = change.getAnimationOptions(); } else { options = info.getAnimationOptions(); } return loadCustomAnimationFromOptions(options, change.getMode()); } @Nullable Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options, @WindowManager.TransitionType int mode) { if (options == null || options.getType() != ANIM_CUSTOM) { return null; } final int resId; if (TransitionUtil.isOpeningType(mode)) { resId = options.getEnterResId(); } else if (TransitionUtil.isClosingType(mode)) { resId = options.getExitResId(); } else if (mode == TRANSIT_CHANGE) { resId = options.getChangeResId(); } else { Log.w(TAG, "Unknown transit type:" + mode); resId = DEFAULT_ANIMATION_RESOURCES_ID; } // Use the default animation if the resources ID is not specified. if (resId == DEFAULT_ANIMATION_RESOURCES_ID) { return null; } final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), isEnter ? options.getEnterResId() : options.getExitResId()); resId); if (anim != null) { return anim; } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +131 −2 Original line number Diff line number Diff line Loading @@ -20,9 +20,11 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds; import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; Loading @@ -32,6 +34,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.animation.Animator; import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; Loading Loading @@ -130,7 +135,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, Loading @@ -148,7 +153,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, Loading @@ -161,4 +166,128 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim // An invalid custom animation is equivalent to jump-cut. assertEquals(0, animator.getDuration()); } @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test public void testCalculateParentBounds_flagDisabled() { final Rect parentBounds = new Rect(0, 0, 2000, 2000); final Rect primaryBounds = new Rect(); final Rect secondaryBounds = new Rect(); parentBounds.splitVertically(primaryBounds, secondaryBounds); final TransitionInfo.Change change = createChange(0 /* flags */); change.setStartAbsBounds(secondaryBounds); final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */); boundsAnimationChange.setStartAbsBounds(primaryBounds); boundsAnimationChange.setEndAbsBounds(primaryBounds); final Rect actualParentBounds = new Rect(); calculateParentBounds(change, boundsAnimationChange, actualParentBounds); assertEquals(parentBounds, actualParentBounds); actualParentBounds.setEmpty(); boundsAnimationChange.setStartAbsBounds(secondaryBounds); boundsAnimationChange.setEndAbsBounds(primaryBounds); calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds); assertEquals(parentBounds, actualParentBounds); } // TODO(b/243518738): Rewrite with TestParameter @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test public void testCalculateParentBounds_flagEnabled() { TransitionInfo.Change change; final TransitionInfo.Change stubChange = createChange(0 /* flags */); final Rect actualParentBounds = new Rect(); Rect parentBounds = new Rect(0, 0, 2000, 2000); Rect endAbsBounds = new Rect(0, 0, 2000, 2000); change = prepareChangeForParentBoundsCalculationTest( new Point(0, 0) /* endRelOffset */, endAbsBounds, new Point() /* endParentSize */ ); calculateParentBounds(change, stubChange, actualParentBounds); assertTrue("Parent bounds must be empty because end parent size is not set.", actualParentBounds.isEmpty()); String testString = "Parent start with (0, 0)"; change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Container not start with (0, 0)"; parentBounds = new Rect(0, 0, 2000, 2000); endAbsBounds = new Rect(1000, 500, 2000, 1500); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Parent container on the right"; parentBounds = new Rect(1000, 0, 2000, 2000); endAbsBounds = new Rect(1000, 500, 1500, 1500); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Parent container on the bottom"; parentBounds = new Rect(0, 1000, 2000, 2000); endAbsBounds = new Rect(500, 1500, 1500, 2000); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); testString = "Parent container in the middle"; parentBounds = new Rect(500, 500, 1500, 1500); endAbsBounds = new Rect(1000, 500, 1500, 1000); change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); calculateParentBounds(change, stubChange, actualParentBounds); assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, actualParentBounds); } @NonNull private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest( @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) { final TransitionInfo.Change change = createChange(0 /* flags */); change.setEndRelOffset(endRelOffset.x, endRelOffset.y); change.setEndAbsBounds(endAbsBounds); change.setEndParentSize(endParentSize.x, endParentSize.y); return change; } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +19 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_NONE; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; Loading @@ -31,6 +32,7 @@ import android.annotation.NonNull; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.WindowContainerToken; Loading Loading @@ -82,11 +84,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { spyOn(mFinishCallback); } /** Creates a mock {@link TransitionInfo.Change}. */ /** * Creates a mock {@link TransitionInfo.Change}. * * @param flags the {@link TransitionInfo.ChangeFlags} of the change */ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) { return createChange(flags, TRANSIT_NONE); } /** * Creates a mock {@link TransitionInfo.Change}. * * @param flags the {@link TransitionInfo.ChangeFlags} of the change * @param mode the transition mode of the change */ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags, @WindowManager.TransitionType int mode) { TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); c.setFlags(flags); c.setMode(mode); return c; } Loading