Fix ActivityEmbedding animation with new split in different layout

When launching an activity into a new TaskFragment with a different
split layout from the existing split, there can be many situations, such
as different split ratio or layout direction.

Use a jump cut for now since it is not a common use case, and it is hard
to cover all situations.

Bug: 241043533
Fix: 258126611
Test: verify with opening new activity to new split layout.
Change-Id: I6c96518b282567339fb890e7f4af7d9d82c98418
This commit is contained in:
Chris Li
2022-12-05 16:06:48 +08:00
parent bac0f96f1e
commit f2296dcb79
2 changed files with 124 additions and 17 deletions

View File

@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
@@ -253,6 +254,10 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
@NonNull
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
if (shouldUseJumpCutForChangeAnimation(targets)) {
return new ArrayList<>();
}
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
for (RemoteAnimationTarget target : targets) {
if (target.mode == MODE_CHANGING) {
@@ -282,4 +287,24 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
return adapters;
}
/**
* Whether we should use jump cut for the change transition.
* This normally happens when opening a new secondary with the existing primary using a
* different split layout. This can be complicated, like from horizontal to vertical split with
* new split pairs.
* Uses a jump cut animation to simplify.
*/
private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
boolean hasOpeningWindow = false;
boolean hasClosingWindow = false;
for (RemoteAnimationTarget target : targets) {
if (target.hasAnimatingParent) {
continue;
}
hasOpeningWindow |= target.mode == MODE_OPENING;
hasClosingWindow |= target.mode == MODE_CLOSING;
}
return hasOpeningWindow && hasClosingWindow;
}
}

View File

@@ -17,6 +17,7 @@
package com.android.wm.shell.activityembedding;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -112,23 +113,30 @@ class ActivityEmbeddingAnimationRunner {
@NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
startTransaction);
addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
adapters);
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
long duration = 0;
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
}
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setDuration(duration);
animator.addUpdateListener((anim) -> {
// Update all adapters in the same transaction.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
long duration = 0;
if (adapters.isEmpty()) {
// Jump cut
// No need to modify the animator, but to update the startTransaction with the changes'
// ending states.
prepareForJumpCut(info, startTransaction);
} else {
addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
postStartTransactionCallbacks, adapters);
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
duration = Math.max(duration, adapter.getDurationHint());
}
t.apply();
});
animator.addUpdateListener((anim) -> {
// Update all adapters in the same transaction.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
}
t.apply();
});
}
animator.setDuration(duration);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@@ -292,6 +300,10 @@ class ActivityEmbeddingAnimationRunner {
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
if (shouldUseJumpCutForChangeTransition(info)) {
return new ArrayList<>();
}
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
@@ -374,9 +386,11 @@ class ActivityEmbeddingAnimationRunner {
}
final Animation animation;
if (change.getParent() != null
&& handledChanges.contains(info.getChange(change.getParent()))) {
// No-op if it will be covered by the changing parent window.
if ((change.getParent() != null
&& handledChanges.contains(info.getChange(change.getParent())))
|| change.getMode() == TRANSIT_CHANGE) {
// No-op if it will be covered by the changing parent window, or it is a changing
// window without bounds change.
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
@@ -421,6 +435,74 @@ class ActivityEmbeddingAnimationRunner {
animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
}
/**
* Whether we should use jump cut for the change transition.
* This normally happens when opening a new secondary with the existing primary using a
* different split layout. This can be complicated, like from horizontal to vertical split with
* new split pairs.
* Uses a jump cut animation to simplify.
*/
private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
// There can be reparenting of changing Activity to new open TaskFragment, so we need to
// exclude both in the first iteration.
final List<TransitionInfo.Change> changingChanges = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
continue;
}
changingChanges.add(change);
final WindowContainerToken parentToken = change.getParent();
if (parentToken != null) {
// When the parent window is also included in the transition as an opening window,
// we would like to animate the parent window instead.
final TransitionInfo.Change parentChange = info.getChange(parentToken);
if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
changingChanges.add(parentChange);
}
}
}
if (changingChanges.isEmpty()) {
// No changing target found.
return true;
}
// Check if the transition contains both opening and closing windows.
boolean hasOpeningWindow = false;
boolean hasClosingWindow = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (changingChanges.contains(change)) {
continue;
}
if (change.getParent() != null
&& changingChanges.contains(info.getChange(change.getParent()))) {
// No-op if it will be covered by the changing parent window.
continue;
}
hasOpeningWindow |= Transitions.isOpeningType(change.getMode());
hasClosingWindow |= Transitions.isClosingType(change.getMode());
}
return hasOpeningWindow && hasClosingWindow;
}
/** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
private void prepareForJumpCut(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction) {
for (TransitionInfo.Change change : info.getChanges()) {
final SurfaceControl leash = change.getLeash();
startTransaction.setPosition(leash,
change.getEndRelOffset().x, change.getEndRelOffset().y);
startTransaction.setWindowCrop(leash,
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
if (change.getMode() == TRANSIT_CLOSE) {
startTransaction.hide(leash);
} else {
startTransaction.show(leash);
startTransaction.setAlpha(leash, 1f);
}
}
}
/** To provide an {@link Animation} based on the transition infos. */
private interface AnimationProvider {
Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,