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

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

Merge "Allow delete empty TaskFragment in PiP" into tm-dev am: d41df352

parents fcd76436 d41df352
Loading
Loading
Loading
Loading
+29 −9
Original line number Diff line number Diff line
@@ -60,7 +60,8 @@ import java.util.function.Consumer;
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
        ActivityEmbeddingComponent {

    private final SplitPresenter mPresenter;
    @VisibleForTesting
    final SplitPresenter mPresenter;

    // Currently applied split configuration.
    private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
@@ -149,6 +150,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            return;
        }

        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final boolean wasInPip = isInPictureInPicture(container);
        container.setInfo(taskFragmentInfo);
        final boolean isInPip = isInPictureInPicture(container);
@@ -159,13 +161,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                // Do not finish the dependents if the last activity is reparented to PiP.
                // Instead, the original split should be cleanup, and the dependent may be expanded
                // to fullscreen.
                cleanupForEnterPip(container);
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
                cleanupForEnterPip(wct, container);
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
            } else {
                // Do not finish the dependents if this TaskFragment was cleared due to launching
                // activity in the Task.
                final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse();
                mPresenter.cleanupContainer(container, shouldFinishDependent);
                mPresenter.cleanupContainer(container, shouldFinishDependent, wct);
            }
        } else if (wasInPip && isInPip) {
            // No update until exit PIP.
@@ -174,12 +176,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // Enter PIP.
            // All overrides will be cleanup.
            container.setLastRequestedBounds(null /* bounds */);
            cleanupForEnterPip(container);
            cleanupForEnterPip(wct, container);
        } else if (wasInPip) {
            // Exit PIP.
            // Updates the presentation of the container. Expand or launch placeholder if needed.
            mPresenter.updateContainer(container);
            updateContainer(wct, container);
        }
        mPresenter.applyTransaction(wct);
        updateCallbackIfNecessary();
    }

@@ -188,7 +191,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
        if (container != null) {
            // Cleanup if the TaskFragment vanished is not requested by the organizer.
            mPresenter.cleanupContainer(container, true /* shouldFinishDependent */);
            removeContainer(container);
            // Make sure the top container is updated.
            final TaskFragmentContainer newTopContainer = getTopActiveContainer(
                    container.getTaskId());
            if (newTopContainer != null) {
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                updateContainer(wct, newTopContainer);
                mPresenter.applyTransaction(wct);
            }
            updateCallbackIfNecessary();
        }
        cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
@@ -452,7 +463,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    }

    /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
    private void cleanupForEnterPip(@NonNull TaskFragmentContainer container) {
    private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentContainer container) {
        final int taskId = container.getTaskId();
        final TaskContainer taskContainer = mTaskContainers.get(taskId);
        if (taskContainer == null) {
@@ -482,7 +494,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
        // since the split is dismissed.
        // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
        mPresenter.updateContainers(containersToUpdate);
        for (TaskFragmentContainer containerToUpdate : containersToUpdate) {
            updateContainer(wct, containerToUpdate);
        }
    }

    /**
@@ -502,6 +516,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // TaskFragment there.
        taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());

        // Cleanup any split references.
        final List<SplitContainer> containersToRemove = new ArrayList<>();
        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
            if (container.equals(splitContainer.getSecondaryContainer())
@@ -510,6 +525,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }
        }
        taskContainer.mSplitContainers.removeAll(containersToRemove);

        // Cleanup any dependent references.
        for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
            containerToUpdate.removeContainerToFinishOnExit(container);
        }
    }

    /**
+6 −14
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Collection;
import java.util.concurrent.Executor;

/**
@@ -73,16 +72,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
    }

    /**
     * Updates the presentation of the provided containers.
     * Deletes the specified container and all other associated and dependent containers in the same
     * transaction.
     */
    void updateContainers(@NonNull Collection<TaskFragmentContainer> containers) {
        if (containers.isEmpty()) {
            return;
        }
    void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        for (TaskFragmentContainer container : containers) {
            mController.updateContainer(wct, container);
        }
        cleanupContainer(container, shouldFinishDependent, wct);
        applyTransaction(wct);
    }

@@ -90,9 +85,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
     * Deletes the specified container and all other associated and dependent containers in the same
     * transaction.
     */
    void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
        final WindowContainerTransaction wct = new WindowContainerTransaction();

    void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent,
            @NonNull WindowContainerTransaction wct) {
        container.finish(shouldFinishDependent, this, wct, mController);

        final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
@@ -100,8 +94,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        if (newTopContainer != null) {
            mController.updateContainer(wct, newTopContainer);
        }

        applyTransaction(wct);
    }

    /**
+4 −8
Original line number Diff line number Diff line
@@ -257,7 +257,8 @@ class TaskFragmentContainer {

        // Finish dependent containers
        for (TaskFragmentContainer container : mContainersToFinishOnExit) {
            if (controller.shouldRetainAssociatedContainer(this, container)) {
            if (container.mIsFinished
                    || controller.shouldRetainAssociatedContainer(this, container)) {
                continue;
            }
            container.finish(true /* shouldFinishDependent */, presenter,
@@ -267,18 +268,13 @@ class TaskFragmentContainer {

        // Finish associated activities
        for (Activity activity : mActivitiesToFinishOnExit) {
            if (controller.shouldRetainAssociatedActivity(this, activity)) {
            if (activity.isFinishing()
                    || controller.shouldRetainAssociatedActivity(this, activity)) {
                continue;
            }
            activity.finish();
        }
        mActivitiesToFinishOnExit.clear();

        // Finish activities that were being re-parented to this container.
        for (Activity activity : mPendingAppearedActivities) {
            activity.finish();
        }
        mPendingAppearedActivities.clear();
    }

    boolean isFinished() {
+35 −1
Original line number Diff line number Diff line
@@ -17,13 +17,20 @@
package androidx.window.extensions.embedding;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;

import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -32,12 +39,14 @@ import androidx.window.extensions.embedding.SplitController.TaskContainer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Test class for {@link SplitController}.
 *
 * Build/Install/Run:
 *  atest WMJetpackUnitTests:SplitController
 *  atest WMJetpackUnitTests:SplitControllerTest
 */
@Presubmit
@SmallTest
@@ -45,12 +54,24 @@ import org.junit.runner.RunWith;
public class SplitControllerTest {
    private static final int TASK_ID = 10;

    @Mock
    private Activity mActivity;
    @Mock
    private Resources mActivityResources;
    @Mock
    private TaskFragmentInfo mInfo;
    private SplitController mSplitController;
    private SplitPresenter mSplitPresenter;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mSplitController = new SplitController();
        mSplitPresenter = mSplitController.mPresenter;
        spyOn(mSplitController);
        spyOn(mSplitPresenter);
        doReturn(mActivityResources).when(mActivity).getResources();
        doReturn(new Configuration()).when(mActivityResources).getConfiguration();
    }

    @Test
@@ -83,4 +104,17 @@ public class SplitControllerTest {
        assertWithMessage("Must return null because tf1 has no running activity.")
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
    }

    @Test
    public void testOnTaskFragmentVanished() {
        final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
        doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();

        // The TaskFragment has been removed in the server, we only need to cleanup the reference.
        mSplitController.onTaskFragmentVanished(mInfo);

        verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
        verify(mSplitController).removeContainer(tf);
        verify(mActivity, never()).finish();
    }
}
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.window.extensions.embedding;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;

import android.app.Activity;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;

/**
 * Test class for {@link TaskFragmentContainer}.
 *
 * Build/Install/Run:
 *  atest WMJetpackUnitTests:TaskFragmentContainerTest
 */
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentContainerTest {
    private static final int TASK_ID = 10;

    @Mock
    private SplitPresenter mPresenter;
    @Mock
    private SplitController mController;
    @Mock
    private Activity mActivity;
    @Mock
    private TaskFragmentInfo mInfo;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testFinish() {
        final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, TASK_ID);
        final WindowContainerTransaction wct = new WindowContainerTransaction();

        // Only remove the activity, but not clear the reference until appeared.
        container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);

        verify(mActivity).finish();
        verify(mPresenter, never()).deleteTaskFragment(any(), any());
        verify(mController, never()).removeContainer(any());

        // Calling twice should not finish activity again.
        clearInvocations(mActivity);
        container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);

        verify(mActivity, never()).finish();
        verify(mPresenter, never()).deleteTaskFragment(any(), any());
        verify(mController, never()).removeContainer(any());

        // Remove all references after the container has appeared in server.
        doReturn(new ArrayList<>()).when(mInfo).getActivities();
        container.setInfo(mInfo);
        container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);

        verify(mActivity, never()).finish();
        verify(mPresenter).deleteTaskFragment(wct, container.getTaskFragmentToken());
        verify(mController).removeContainer(container);
    }
}
Loading