Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +29 −9 Original line number Diff line number Diff line Loading @@ -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<>(); Loading Loading @@ -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); Loading @@ -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. Loading @@ -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(); } Loading @@ -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()); Loading Loading @@ -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) { Loading Loading @@ -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); } } /** Loading @@ -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()) Loading @@ -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); } } /** Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +6 −14 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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); } Loading @@ -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( Loading @@ -100,8 +94,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (newTopContainer != null) { mController.updateContainer(wct, newTopContainer); } applyTransaction(wct); } /** Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +4 −8 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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() { Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +35 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading Loading @@ -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(); } } libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java 0 → 100644 +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
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +29 −9 Original line number Diff line number Diff line Loading @@ -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<>(); Loading Loading @@ -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); Loading @@ -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. Loading @@ -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(); } Loading @@ -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()); Loading Loading @@ -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) { Loading Loading @@ -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); } } /** Loading @@ -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()) Loading @@ -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); } } /** Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +6 −14 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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); } Loading @@ -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( Loading @@ -100,8 +94,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (newTopContainer != null) { mController.updateContainer(wct, newTopContainer); } applyTransaction(wct); } /** Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +4 −8 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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() { Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +35 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading Loading @@ -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(); } }
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java 0 → 100644 +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); } }