Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0dd47a2d authored by Riddle Hsu's avatar Riddle Hsu
Browse files

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
parent 7dd18dd3
Loading
Loading
Loading
Loading
+44 −5
Original line number Diff line number Diff line
@@ -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,
+23 −3
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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")