Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +15 −9 Original line number Diff line number Diff line Loading @@ -308,7 +308,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final List<TaskFragmentContainer> containers = taskContainer.mContainers; // Clean up the TaskFragmentContainers by the z-order from the lowest. for (int i = 0; i < containers.size() - 1; i++) { for (int i = 0; i < containers.size(); i++) { final TaskFragmentContainer container = containers.get(i); if (pendingFinishingContainers.contains(container)) { // Don't update records here to prevent double invocation. Loading @@ -318,7 +318,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Remove container records. removeContainers(taskContainer, pendingFinishingContainers); // Update the change to the client side. // Update the change to the server side. updateContainersInTaskIfVisible(wct, taskContainer.getTaskId()); } }); Loading Loading @@ -353,21 +353,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void updateSplitAttributes(@NonNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes) { Objects.requireNonNull(splitInfoToken); Objects.requireNonNull(splitAttributes); synchronized (mLock) { final SplitContainer splitContainer = getSplitContainer(splitInfoToken); if (splitContainer == null) { Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken); return; } WindowContainerTransaction wct = mTransactionManager.startNewTransaction() .getTransaction(); if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) { // Override the default split Attributes so that it will be applied // if the SplitContainer is not visible currently. splitContainer.updateDefaultSplitAttributes(splitAttributes); mTransactionManager.getCurrentTransactionRecord() .apply(false /* shouldApplyIndependently */); final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); final WindowContainerTransaction wct = transactionRecord.getTransaction(); if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) { transactionRecord.apply(false /* shouldApplyIndependently */); } else { // Abort if the SplitContainer wasn't updated. mTransactionManager.getCurrentTransactionRecord().abort(); transactionRecord.abort(); } } } Loading Loading @@ -1559,8 +1563,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * * @return {@code true} if the update succeed. Otherwise, returns {@code false}. */ @VisibleForTesting @GuardedBy("mLock") private boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer, boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer, @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) { if (!isTopMostSplit(splitContainer)) { // Skip position update - it isn't the topmost split. Loading Loading @@ -1904,6 +1909,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } @VisibleForTesting @Nullable @GuardedBy("mLock") SplitContainer getSplitContainer(@NonNull IBinder token) { Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +123 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import android.annotation.NonNull; import android.app.Activity; Loading @@ -82,6 +83,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.view.WindowInsets; import android.view.WindowMetrics; import android.window.TaskFragmentInfo; Loading @@ -100,12 +102,14 @@ import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Consumer; /** Loading Loading @@ -1323,6 +1327,125 @@ public class SplitControllerTest { verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any()); } @Test public void testFinishActivityStacks_emptySet_earlyReturn() { mSplitController.finishActivityStacks(Collections.emptySet()); verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt()); } @Test public void testFinishActivityStacks_invalidStacks_earlyReturn() { mSplitController.finishActivityStacks(Collections.singleton(new Binder())); verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt()); } @Test public void testFinishActivityStacks_finishSingleActivityStack() { TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) .mContainers; assertEquals(containers.get(0), tf); mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken())); verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken())); assertTrue(containers.isEmpty()); } @Test public void testFinishActivityStacks_finishActivityStacksInOrder() { TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); TaskFragmentContainer topTf = mSplitController.newContainer(mActivity, TASK_ID); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) .mContainers; assertEquals(containers.size(), 2); Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{ topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()}); mSplitController.finishActivityStacks(activityStackTokens); ArgumentCaptor<IBinder> argumentCaptor = ArgumentCaptor.forClass(IBinder.class); verify(mSplitPresenter, times(2)).deleteTaskFragment(any(), argumentCaptor.capture()); List<IBinder> fragmentTokens = argumentCaptor.getAllValues(); assertEquals("The ActivityStack must be deleted from the lowest z-order " + "regardless of the order in ActivityStack set", bottomTf.getTaskFragmentToken(), fragmentTokens.get(0)); assertEquals("The ActivityStack must be deleted from the lowest z-order " + "regardless of the order in ActivityStack set", topTf.getTaskFragmentToken(), fragmentTokens.get(1)); assertTrue(containers.isEmpty()); } @Test public void testUpdateSplitAttributes_invalidSplitContainerToken_earlyReturn() { mSplitController.updateSplitAttributes(new Binder(), SPLIT_ATTRIBUTES); verify(mTransactionManager, never()).startNewTransaction(); } @Test public void testUpdateSplitAttributes_nullParams_throwException() { assertThrows(NullPointerException.class, () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES)); final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); doReturn(token).when(splitContainer).getToken(); doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); assertThrows(NullPointerException.class, () -> mSplitController.updateSplitAttributes(token, null)); } @Test public void testUpdateSplitAttributes_doNotNeedToUpdateSplitContainer_doNotApplyTransaction() { final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); doReturn(token).when(splitContainer).getToken(); doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); doReturn(false).when(mSplitController).updateSplitContainerIfNeeded( eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES)); TransactionManager.TransactionRecord testRecord = mock(TransactionManager.TransactionRecord.class); doReturn(testRecord).when(mTransactionManager).startNewTransaction(); mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES); verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES)); verify(testRecord).abort(); } @Test public void testUpdateSplitAttributes_splitContainerUpdated_updateAttrs() { final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); doReturn(token).when(splitContainer).getToken(); doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); doReturn(true).when(mSplitController).updateSplitContainerIfNeeded( eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES)); TransactionManager.TransactionRecord testRecord = mock(TransactionManager.TransactionRecord.class); doReturn(testRecord).when(mTransactionManager).startNewTransaction(); mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES); verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES)); verify(testRecord).apply(eq(false)); } /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); Loading Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +15 −9 Original line number Diff line number Diff line Loading @@ -308,7 +308,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final List<TaskFragmentContainer> containers = taskContainer.mContainers; // Clean up the TaskFragmentContainers by the z-order from the lowest. for (int i = 0; i < containers.size() - 1; i++) { for (int i = 0; i < containers.size(); i++) { final TaskFragmentContainer container = containers.get(i); if (pendingFinishingContainers.contains(container)) { // Don't update records here to prevent double invocation. Loading @@ -318,7 +318,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Remove container records. removeContainers(taskContainer, pendingFinishingContainers); // Update the change to the client side. // Update the change to the server side. updateContainersInTaskIfVisible(wct, taskContainer.getTaskId()); } }); Loading Loading @@ -353,21 +353,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void updateSplitAttributes(@NonNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes) { Objects.requireNonNull(splitInfoToken); Objects.requireNonNull(splitAttributes); synchronized (mLock) { final SplitContainer splitContainer = getSplitContainer(splitInfoToken); if (splitContainer == null) { Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken); return; } WindowContainerTransaction wct = mTransactionManager.startNewTransaction() .getTransaction(); if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) { // Override the default split Attributes so that it will be applied // if the SplitContainer is not visible currently. splitContainer.updateDefaultSplitAttributes(splitAttributes); mTransactionManager.getCurrentTransactionRecord() .apply(false /* shouldApplyIndependently */); final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); final WindowContainerTransaction wct = transactionRecord.getTransaction(); if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) { transactionRecord.apply(false /* shouldApplyIndependently */); } else { // Abort if the SplitContainer wasn't updated. mTransactionManager.getCurrentTransactionRecord().abort(); transactionRecord.abort(); } } } Loading Loading @@ -1559,8 +1563,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * * @return {@code true} if the update succeed. Otherwise, returns {@code false}. */ @VisibleForTesting @GuardedBy("mLock") private boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer, boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer, @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) { if (!isTopMostSplit(splitContainer)) { // Skip position update - it isn't the topmost split. Loading Loading @@ -1904,6 +1909,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } @VisibleForTesting @Nullable @GuardedBy("mLock") SplitContainer getSplitContainer(@NonNull IBinder token) { Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +123 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import android.annotation.NonNull; import android.app.Activity; Loading @@ -82,6 +83,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.view.WindowInsets; import android.view.WindowMetrics; import android.window.TaskFragmentInfo; Loading @@ -100,12 +102,14 @@ import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Consumer; /** Loading Loading @@ -1323,6 +1327,125 @@ public class SplitControllerTest { verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any()); } @Test public void testFinishActivityStacks_emptySet_earlyReturn() { mSplitController.finishActivityStacks(Collections.emptySet()); verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt()); } @Test public void testFinishActivityStacks_invalidStacks_earlyReturn() { mSplitController.finishActivityStacks(Collections.singleton(new Binder())); verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt()); } @Test public void testFinishActivityStacks_finishSingleActivityStack() { TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) .mContainers; assertEquals(containers.get(0), tf); mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken())); verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken())); assertTrue(containers.isEmpty()); } @Test public void testFinishActivityStacks_finishActivityStacksInOrder() { TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); TaskFragmentContainer topTf = mSplitController.newContainer(mActivity, TASK_ID); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) .mContainers; assertEquals(containers.size(), 2); Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{ topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()}); mSplitController.finishActivityStacks(activityStackTokens); ArgumentCaptor<IBinder> argumentCaptor = ArgumentCaptor.forClass(IBinder.class); verify(mSplitPresenter, times(2)).deleteTaskFragment(any(), argumentCaptor.capture()); List<IBinder> fragmentTokens = argumentCaptor.getAllValues(); assertEquals("The ActivityStack must be deleted from the lowest z-order " + "regardless of the order in ActivityStack set", bottomTf.getTaskFragmentToken(), fragmentTokens.get(0)); assertEquals("The ActivityStack must be deleted from the lowest z-order " + "regardless of the order in ActivityStack set", topTf.getTaskFragmentToken(), fragmentTokens.get(1)); assertTrue(containers.isEmpty()); } @Test public void testUpdateSplitAttributes_invalidSplitContainerToken_earlyReturn() { mSplitController.updateSplitAttributes(new Binder(), SPLIT_ATTRIBUTES); verify(mTransactionManager, never()).startNewTransaction(); } @Test public void testUpdateSplitAttributes_nullParams_throwException() { assertThrows(NullPointerException.class, () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES)); final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); doReturn(token).when(splitContainer).getToken(); doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); assertThrows(NullPointerException.class, () -> mSplitController.updateSplitAttributes(token, null)); } @Test public void testUpdateSplitAttributes_doNotNeedToUpdateSplitContainer_doNotApplyTransaction() { final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); doReturn(token).when(splitContainer).getToken(); doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); doReturn(false).when(mSplitController).updateSplitContainerIfNeeded( eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES)); TransactionManager.TransactionRecord testRecord = mock(TransactionManager.TransactionRecord.class); doReturn(testRecord).when(mTransactionManager).startNewTransaction(); mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES); verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES)); verify(testRecord).abort(); } @Test public void testUpdateSplitAttributes_splitContainerUpdated_updateAttrs() { final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); doReturn(token).when(splitContainer).getToken(); doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); doReturn(true).when(mSplitController).updateSplitContainerIfNeeded( eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES)); TransactionManager.TransactionRecord testRecord = mock(TransactionManager.TransactionRecord.class); doReturn(testRecord).when(mTransactionManager).startNewTransaction(); mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES); verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES)); verify(testRecord).apply(eq(false)); } /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); Loading