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

Commit 494ce075 authored by Chris Li's avatar Chris Li
Browse files

Include activity reparent in transition

When an activity is reparented from a Task to a TaskFragment of
different bounds, freeze the surface and include it in app transition.

Bug: 196173550
Test: test with demo app
Test: AppTransitionTests#testActivityRecordReparentToTaskFragment
Change-Id: I73edc2c4091f0e4156ecf963ce4d1fcbd00333c8
parent 28576187
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -1389,6 +1389,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        return parent != null ? parent.asTaskFragment() : null;
    }

    /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */
    private boolean shouldStartChangeTransition(
            @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) {
        if (mWmService.mDisableTransitionAnimation
                || mDisplayContent == null || newParent == null || oldParent == null
                || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) {
            return false;
        }

        // Transition change for the activity moving into a TaskFragment of different bounds.
        return newParent.isOrganizedTaskFragment()
                && !newParent.getBounds().equals(oldParent.getBounds());
    }

    @Override
    void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) {
        final TaskFragment oldParent = (TaskFragment) rawOldParent;
@@ -1397,6 +1411,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        final Task newTask = newParent != null ? newParent.getTask() : null;
        this.task = newTask;

        if (shouldStartChangeTransition(newParent, oldParent)) {
            initializeChangeTransition(getBounds());
        }

        super.onParentChanged(newParent, oldParent);

        if (isPersistable()) {
@@ -2607,6 +2625,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        return parent != null ? parent.getOrganizedTaskFragment() : null;
    }

    @Override
    boolean isEmbedded() {
        final TaskFragment parent = getTaskFragment();
        return parent != null && parent.isEmbedded();
+15 −0
Original line number Diff line number Diff line
@@ -237,6 +237,8 @@ public class AppTransitionController {

        // Check if there is any override
        if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
            // Unfreeze the windows that were previously frozen for TaskFragment animation.
            unfreezeEmbeddedChangingWindows();
            overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
        }

@@ -341,6 +343,9 @@ public class AppTransitionController {
            switch (changingType) {
                case TYPE_TASK:
                    return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
                case TYPE_ACTIVITY:
                    // ActivityRecord is put in a change transition only when it is reparented
                    // to an organized TaskFragment. See ActivityRecord#shouldStartChangeTransition.
                case TYPE_TASK_FRAGMENT:
                    return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
                default:
@@ -511,6 +516,16 @@ public class AppTransitionController {
                : null;
    }

    private void unfreezeEmbeddedChangingWindows() {
        final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers;
        for (int i = changingContainers.size() - 1; i >= 0; i--) {
            final WindowContainer wc = changingContainers.valueAt(i);
            if (wc.isEmbedded()) {
                wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction());
            }
        }
    }

    /**
     * Overrides the pending transition with the remote animation defined by the
     * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+6 −11
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -465,7 +464,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        return this;
    }

    /** Returns {@code true} if this is a container for embedded activities or tasks. */
    @Override
    boolean isEmbedded() {
        if (mIsEmbedded) {
            return true;
@@ -2073,15 +2072,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        return !startBounds.equals(getBounds());
    }

    /**
     * Initializes a change transition. See {@link SurfaceFreezer} for more information.
     */
    void initializeChangeTransition(Rect startBounds) {
        mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
        mDisplayContent.mChangingContainers.add(this);
        mSurfaceFreezer.freeze(getSyncTransaction(), startBounds);
    }

    @Override
    void setSurfaceControl(SurfaceControl sc) {
        super.setSurfaceControl(sc);
@@ -2163,6 +2153,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        return mTaskFragmentOrganizer != null;
    }

    /** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */
    final boolean isOrganizedTaskFragment() {
        return mTaskFragmentOrganizer != null;
    }

    /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
    void clearLastPausedActivity() {
        forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
+28 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;

import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -2578,14 +2579,35 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
        mSurfaceFreezer.unfreeze(getPendingTransaction());
    }

    /**
     * Initializes a change transition. See {@link SurfaceFreezer} for more information.
     *
     * For now, this will only be called for the following cases:
     * 1. {@link Task} is changing windowing mode between fullscreen and freeform.
     * 2. {@link TaskFragment} is organized and is changing window bounds.
     * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}.
     *
     * This shouldn't be called on other {@link WindowContainer} unless there is a valid use case.
     */
    void initializeChangeTransition(Rect startBounds) {
        mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
        mDisplayContent.mChangingContainers.add(this);
        mSurfaceFreezer.freeze(getSyncTransaction(), startBounds);
    }

    ArraySet<WindowContainer> getAnimationSources() {
        return mSurfaceAnimationSources;
    }

    @Override
    public SurfaceControl getFreezeSnapshotTarget() {
        // Only allow freezing if this window is in a TRANSIT_CHANGE
        if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)
                || !mDisplayContent.mChangingContainers.contains(this)) {
            return null;
        }
        return getSurfaceControl();
    }

    @Override
    public Builder makeAnimationLeash() {
@@ -3189,6 +3211,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
        return false;
    }

    /** @return {@code true} if this is a container for embedded activities or tasks. */
    boolean isEmbedded() {
        return false;
    }

    /**
     * @return {@code true} if this container's surface should be shown when it is created.
     */
+32 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;

import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -54,6 +55,8 @@ import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;

import androidx.test.filters.SmallTest;

@@ -391,6 +394,35 @@ public class AppTransitionTests extends WindowTestsBase {
                mDc.mAppTransition.getAnimationStyleResId(attrs));
    }

    @Test
    public void testActivityRecordReparentToTaskFragment() {
        final ActivityRecord activity = createActivityRecord(mDc);
        activity.setVisibility(true);
        final Task task = activity.getTask();

        // Add a TaskFragment of half of the Task size.
        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
        final ITaskFragmentOrganizer iOrganizer =
                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                .setParentTask(task)
                .setOrganizer(organizer)
                .build();
        final Rect taskBounds = new Rect();
        task.getBounds(taskBounds);
        taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);

        assertTrue(mDc.mChangingContainers.isEmpty());
        assertFalse(mDc.mAppTransition.isTransitionSet());

        // Schedule app transition when reparent activity to a TaskFragment of different size.
        activity.reparent(taskFragment, POSITION_TOP);

        assertTrue(mDc.mChangingContainers.contains(activity));
        assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
    }

    private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
        boolean mCancelled = false;
        @Override