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

Commit d41df352 authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

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

parents 5778a88d 720ae87d
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