From f2296dcb79ddd7225172aad4c4809f54ca3faee7 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 5 Dec 2022 16:06:48 +0800 Subject: [PATCH] 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 --- .../TaskFragmentAnimationRunner.java | 25 ++++ .../ActivityEmbeddingAnimationRunner.java | 116 +++++++++++++++--- 2 files changed, 124 insertions(+), 17 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 322f85449450e..dcc12ac075891 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -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 createChangeAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { + if (shouldUseJumpCutForChangeAnimation(targets)) { + return new ArrayList<>(); + } + final List 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; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index c0a6456a2df0a..164d2f149931f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -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> postStartTransactionCallbacks) { final List 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 createChangeAnimationAdapters( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + if (shouldUseJumpCutForChangeTransition(info)) { + return new ArrayList<>(); + } + final List adapters = new ArrayList<>(); final Set 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 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,