Accept occluded closing non-embedded change for embedded transition

Launch flow:
a. A translucent host activity.
b. Create 2 split TaskFragment.
c. Move the host into one split and put a new activity on another.

The changes will have:
2 OPEN [embedded]TaskFragment
1 CHANGE [embedded]ActivityRecord
1 CLOSE Task

The CLOSE task was visible because the host is translucent.
Then after trigger embedded split, it is occluded by the
embedded activities.

Previously, ActivityEmbeddingController rejected to animate
if the transition contain one non-embedded change. Then it
will go default transition handler to apply weird animation.

Because the CLOSE Task is simply occluded, the needed operation
is to hide its surface by finishTransaction. Hence simply
ignore it and let ActivityEmbeddingController animate the
embedded changes.

Also make the screenshot of CHANGE non-opaque if the container
is translucent, so the CLOSE Task won't disappear suddenly due
to the opaque layer above.

Fix: 271818877
Test: atest ActivityEmbeddingControllerTests# \
      testStartAnimation_containsNonActivityEmbeddingChange

Change-Id: I13781969b8e515a3bf33cc93ee8d4e302548bcbd
This commit is contained in:
Riddle Hsu
2023-04-12 22:44:35 +08:00
parent 7dd18dd348
commit 0dd47a2ddd
3 changed files with 68 additions and 9 deletions

View File

@@ -22,6 +22,7 @@ import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static java.util.Objects.requireNonNull;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
@@ -35,6 +36,9 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
/**
* Responsible for handling ActivityEmbedding related transitions.
@@ -86,12 +90,13 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean containsEmbeddingSplit = false;
for (TransitionInfo.Change change : info.getChanges()) {
boolean containsNonEmbeddedChange = false;
final List<TransitionInfo.Change> changes = info.getChanges();
for (int i = changes.size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = changes.get(i);
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)) {
containsNonEmbeddedChange = true;
} else if (!change.hasFlags(FLAG_FILLS_TASK)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
containsEmbeddingSplit = true;
@@ -103,6 +108,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
// such as the device is in a folded state.
return false;
}
if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
return false;
}
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
@@ -110,6 +118,37 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
return true;
}
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = changes.get(i);
if (!TransitionUtil.isClosingType(change.getMode())) {
if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
nonClosingEmbeddedArea.union(change.getEndAbsBounds());
continue;
}
// Not able to handle non-embedded container if it is not closing.
return false;
}
}
for (int i = changes.size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = changes.get(i);
if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
&& !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) {
// Unknown to animate containers outside the area of embedded activities.
return false;
}
}
// Drop the non-embedded closing change because it is occluded by embedded activities.
for (int i = changes.size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = changes.get(i);
if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
changes.remove(i);
}
}
return true;
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -82,10 +83,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsNonActivityEmbeddingChange() {
final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */);
final TransitionInfo.Change embeddedOpen = createEmbeddedChange(
EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
nonEmbeddedOpen.setMode(TRANSIT_OPEN);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
.addChange(createEmbeddedChange(
EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
.addChange(createChange(0 /* flags */))
.addChange(embeddedOpen)
.addChange(nonEmbeddedOpen)
.build();
// No-op because it contains non-embedded change.
@@ -95,6 +99,22 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mStartTransaction);
verifyNoMoreInteractions(mFinishTransaction);
verifyNoMoreInteractions(mFinishCallback);
final TransitionInfo.Change nonEmbeddedClose = createChange(0 /* flags */);
nonEmbeddedClose.setMode(TRANSIT_CLOSE);
nonEmbeddedClose.setEndAbsBounds(TASK_BOUNDS);
final TransitionInfo.Change embeddedOpen2 = createEmbeddedChange(
EMBEDDED_RIGHT_BOUNDS, EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS);
final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
.addChange(embeddedOpen)
.addChange(embeddedOpen2)
.addChange(nonEmbeddedClose)
.build();
// Ok to animate because nonEmbeddedClose is occluded by embeddedOpen and embeddedOpen2.
assertTrue(mController.startAnimation(mTransition, info2, mStartTransaction,
mFinishTransaction, mFinishCallback));
// The non-embedded change is dropped to avoid affecting embedded animation.
assertFalse(info2.getChanges().contains(nonEmbeddedClose));
}
@Test

View File

@@ -2824,7 +2824,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
SurfaceControl snapshotSurface = wc.makeAnimationLeash()
.setName(name)
.setOpaque(true)
.setOpaque(wc.fillsParent())
.setParent(wc.getSurfaceControl())
.setSecure(screenshotBuffer.containsSecureLayers())
.setCallsite("Transition.ScreenshotSync")