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

Commit debbcadf authored by Chris Li's avatar Chris Li Committed by Automerger Merge Worker
Browse files

Merge "Fix transition wait when ActivityEmbedding enters PiP" into tm-dev am: 849cb514

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17801185



Change-Id: Ic4e4d09aa6664b2f3e04daea49e00cb474511f7f
Ignore-AOSP-First: this is an automerge
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 0b8c7e2c 849cb514
Loading
Loading
Loading
Loading
+3 −7
Original line number Original line Diff line number Diff line
@@ -562,9 +562,6 @@ public class AppTransitionController {
                leafTask = null;
                leafTask = null;
                break;
                break;
            }
            }
            // The activity may be a child of embedded Task, but we want to find the owner Task.
            // As a result, find the organized TaskFragment first.
            final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
            // There are also cases where the Task contains non-embedded activity, such as launching
            // There are also cases where the Task contains non-embedded activity, such as launching
            // split TaskFragments from a non-embedded activity.
            // split TaskFragments from a non-embedded activity.
            // The hierarchy may looks like this:
            // The hierarchy may looks like this:
@@ -575,10 +572,9 @@ public class AppTransitionController {
            //    - TaskFragment
            //    - TaskFragment
            //       - Activity
            //       - Activity
            // We also want to have the organizer handle the transition for such case.
            // We also want to have the organizer handle the transition for such case.
            final Task task = organizedTaskFragment != null
            final Task task = r.getTask();
                    ? organizedTaskFragment.getTask()
            // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
                    : r.getTask();
            if (task == null || task.inPinnedWindowingMode()) {
            if (task == null) {
                leafTask = null;
                leafTask = null;
                break;
                break;
            }
            }
+10 −6
Original line number Original line Diff line number Diff line
@@ -2097,11 +2097,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
            r.setWindowingMode(intermediateWindowingMode);
            r.setWindowingMode(intermediateWindowingMode);
            r.mWaitForEnteringPinnedMode = true;
            r.mWaitForEnteringPinnedMode = true;
            rootTask.forAllTaskFragments(tf -> {
            rootTask.forAllTaskFragments(tf -> {
                // When the Task is entering picture-in-picture, we should clear all override from
                if (!tf.isOrganizedTaskFragment()) {
                // the client organizer, so the PIP activity can get the correct config from the
                    return;
                // Task, and prevent conflict with the PipTaskOrganizer.
                }
                if (tf.isOrganizedTaskFragment()) {
                tf.resetAdjacentTaskFragment();
                tf.resetAdjacentTaskFragment();
                if (tf.getTopNonFinishingActivity() != null) {
                    // When the Task is entering picture-in-picture, we should clear all override
                    // from the client organizer, so the PIP activity can get the correct config
                    // from the Task, and prevent conflict with the PipTaskOrganizer.
                    tf.updateRequestedOverrideConfiguration(EMPTY);
                    tf.updateRequestedOverrideConfiguration(EMPTY);
                }
                }
            });
            });
@@ -2116,7 +2119,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
            // to the root pinned task
            // to the root pinned task
            r.supportsEnterPipOnTaskSwitch = false;
            r.supportsEnterPipOnTaskSwitch = false;


            if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) {
            if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip
                    && organizedTf.isTaskVisibleRequested()) {
                // Dispatch the pending info to TaskFragmentOrganizer before PIP animation.
                // Dispatch the pending info to TaskFragmentOrganizer before PIP animation.
                // Otherwise, it will keep waiting for the empty TaskFragment to be non-empty.
                // Otherwise, it will keep waiting for the empty TaskFragment to be non-empty.
                mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
                mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
+34 −2
Original line number Original line Diff line number Diff line
@@ -2302,11 +2302,32 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        return mTaskFragmentOrganizer != null;
        return mTaskFragmentOrganizer != null;
    }
    }


    /** Whether the Task should be visible. */
    boolean isTaskVisibleRequested() {
        final Task task = getTask();
        return task != null && task.isVisibleRequested();
    }

    boolean isReadyToTransit() {
    boolean isReadyToTransit() {
        // We only wait when this is organized to give the organizer a chance to update.
        if (!isOrganizedTaskFragment()) {
            return true;
        }
        // We don't want to start the transition if the organized TaskFragment is empty, unless
        // We don't want to start the transition if the organized TaskFragment is empty, unless
        // it is requested to be removed.
        // it is requested to be removed.
        return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null
        if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
                || mIsRemovalRequested;
            return true;
        }
        // Organizer shouldn't change embedded TaskFragment in PiP.
        if (isEmbeddedTaskFragmentInPip()) {
            return true;
        }
        // The TaskFragment becomes empty because the last running activity enters PiP when the Task
        // is minimized.
        if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
            return true;
        }
        return false;
    }
    }


    /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
    /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
@@ -2424,8 +2445,19 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        mIsRemovalRequested = false;
        mIsRemovalRequested = false;
        resetAdjacentTaskFragment();
        resetAdjacentTaskFragment();
        cleanUp();
        cleanUp();
        final boolean shouldExecuteAppTransition =
                mClearedTaskFragmentForPip && isTaskVisibleRequested();
        super.removeImmediately();
        super.removeImmediately();
        sendTaskFragmentVanished();
        sendTaskFragmentVanished();
        if (shouldExecuteAppTransition && mDisplayContent != null) {
            // When the Task is still visible, and the TaskFragment is removed because the last
            // running activity is reparenting to PiP, it is possible that no activity is getting
            // paused or resumed (having an embedded activity in split), thus we need to relayout
            // and execute it explicitly.
            mAtmService.addWindowLayoutReasons(
                    ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
            mDisplayContent.executeAppTransition();
        }
    }
    }


    /** Called on remove to cleanup. */
    /** Called on remove to cleanup. */
+6 −1
Original line number Original line Diff line number Diff line
@@ -518,8 +518,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
        // longer has activities. As a result, the organizer will never get this info changed event
        // longer has activities. As a result, the organizer will never get this info changed event
        // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
        // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
        // has running activities.
        // has running activities.
        // Another case is when an organized TaskFragment became empty because the last running
        // activity is reparented to a new Task due to enter PiP. We also want to notify the
        // organizer, so it can remove the empty TaskFragment and update the paired TaskFragment
        // without causing the extra delay.
        return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
        return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
                && task.topRunningActivity() == null && lastInfo != null
                && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip())
                && lastInfo != null
                && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
                && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
    }
    }


+42 −0
Original line number Original line Diff line number Diff line
@@ -19,8 +19,10 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;


import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -30,6 +32,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.clearInvocations;


import android.content.res.Configuration;
import android.content.res.Configuration;
@@ -257,6 +260,9 @@ public class TaskFragmentTest extends WindowTestsBase {
                .createActivityCount(1)
                .createActivityCount(1)
                .build();
                .build();
        final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
        final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
        activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
        activity1.setVisibility(true /* visible */, false /* deferHidingClient */);
        spyOn(mAtm.mTaskFragmentOrganizerController);
        spyOn(mAtm.mTaskFragmentOrganizerController);


        // Move activity to pinned.
        // Move activity to pinned.
@@ -269,10 +275,46 @@ public class TaskFragmentTest extends WindowTestsBase {
        final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo();
        final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo();
        assertTrue(info.isTaskFragmentClearedForPip());
        assertTrue(info.isTaskFragmentClearedForPip());
        assertTrue(info.isEmpty());
        assertTrue(info.isEmpty());

        // Notify organizer because the Task is still visible.
        assertTrue(task.isVisibleRequested());
        verify(mAtm.mTaskFragmentOrganizerController)
        verify(mAtm.mTaskFragmentOrganizerController)
                .dispatchPendingInfoChangedEvent(taskFragment0);
                .dispatchPendingInfoChangedEvent(taskFragment0);
    }
    }


    @Test
    public void testIsReadyToTransit() {
        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                .setCreateParentTask()
                .setOrganizer(mOrganizer)
                .setFragmentToken(new Binder())
                .build();
        final Task task = taskFragment.getTask();

        // Not ready when it is empty.
        assertFalse(taskFragment.isReadyToTransit());

        // Ready when it is not empty.
        final ActivityRecord activity = createActivityRecord(mDisplayContent);
        doNothing().when(activity).setDropInputMode(anyInt());
        activity.reparent(taskFragment, WindowContainer.POSITION_TOP);
        assertTrue(taskFragment.isReadyToTransit());

        // Ready when the Task is in PiP.
        taskFragment.removeChild(activity);
        task.setWindowingMode(WINDOWING_MODE_PINNED);
        assertTrue(taskFragment.isReadyToTransit());

        // Ready when the TaskFragment is empty because of PiP, and the Task is invisible.
        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        taskFragment.mClearedTaskFragmentForPip = true;
        assertTrue(taskFragment.isReadyToTransit());

        // Not ready if the task is still visible when the TaskFragment becomes empty.
        doReturn(true).when(task).isVisibleRequested();
        assertFalse(taskFragment.isReadyToTransit());
    }

    @Test
    @Test
    public void testActivityHasOverlayOverUntrustedModeEmbedded() {
    public void testActivityHasOverlayOverUntrustedModeEmbedded() {
        final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
        final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,