Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +40 −19 Original line number Diff line number Diff line Loading @@ -530,11 +530,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == splitContainer.getPrimaryContainer()) { // The new launched can be in the primary container when it is starting a new activity // onCreate, thus the secondary may still be empty. // onCreate. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent(); if (secondaryIntent != null) { // Check with the pending Intent before it is started on the server side. // This can happen if the launched Activity start a new Intent to secondary during // #onCreated(). return getSplitRule(launchedActivity, secondaryIntent) != null; } final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity(); return secondaryActivity == null || getSplitRule(launchedActivity, secondaryActivity) != null; return secondaryActivity != null && getSplitRule(launchedActivity, secondaryActivity) != null; } // Check if the new launched activity is a placeholder. Loading Loading @@ -573,7 +580,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen Activity activityBelow = null; final TaskFragmentContainer container = getContainerWithActivity(activity); if (container != null) { final List<Activity> containerActivities = container.collectActivities(); final List<Activity> containerActivities = container.collectNonFinishingActivities(); final int index = containerActivities.indexOf(activity); if (index > 0) { activityBelow = containerActivities.get(index - 1); Loading Loading @@ -691,7 +698,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // 1. Whether the new activity intent should always expand. if (shouldExpand(null /* activity */, intent)) { return createEmptyExpandedContainer(wct, taskId, launchingActivity); return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity); } // 2. Whether the launching activity (if set) should be split with the new activity intent. Loading Loading @@ -742,7 +749,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Nullable private TaskFragmentContainer createEmptyExpandedContainer( @NonNull WindowContainerTransaction wct, int taskId, @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity) { // We need an activity in the organizer process in the same Task to use as the owner // activity, as well as to get the Task window info. Loading @@ -759,8 +766,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find any activity in the Task that we can use as the owner activity. return null; } final TaskFragmentContainer expandedContainer = newContainer(null /* activity */, activityInTask, taskId); final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask, taskId); mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(), activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); return expandedContainer; Loading Loading @@ -789,7 +796,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return splitContainer.getSecondaryContainer(); } // Create a new TaskFragment to split with the primary activity for the new activity. return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, splitRule); return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, splitRule); } /** Loading @@ -813,21 +821,34 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) { return newContainer(activity, activity, taskId); TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) { return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); } TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId) { return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, activityInTask, taskId); } TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, activityInTask, taskId); } /** * Creates and registers a new organized container with an optional activity that will be * re-parented to it in a WCT. * * @param activity the activity that will be reparented to the TaskFragment. * @param activityInTask activity in the same Task so that we can get the Task bounds if * needed. * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. * @param activityInTask activity in the same Task so that we can get the Task bounds * if needed. * @param taskId parent Task of the new TaskFragment. */ TaskFragmentContainer newContainer(@Nullable Activity activity, @NonNull Activity activityInTask, int taskId) { TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } Loading @@ -835,8 +856,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mTaskContainers.put(taskId, new TaskContainer(taskId)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskContainer, this); final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, pendingAppearedIntent, taskContainer, this); if (!taskContainer.isTaskBoundsInitialized()) { // Get the initial bounds before the TaskFragment has appeared. final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +3 −3 Original line number Diff line number Diff line Loading @@ -101,7 +101,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull TaskFragmentContainer createNewSplitWithEmptySideContainer( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull SplitPairRule rule) { @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { final Rect parentBounds = getParentContainerBounds(primaryActivity); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, isLtr(primaryActivity, rule)); Loading @@ -111,7 +111,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( null /* activity */, primaryActivity, taskId); secondaryIntent, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); final int windowingMode = mController.getTaskContainer(taskId) Loading Loading @@ -224,7 +224,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } final int taskId = primaryContainer.getTaskId(); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent, launchingActivity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +45 −21 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; Loading Loading @@ -64,7 +65,16 @@ class TaskFragmentContainer { * Activities that are being reparented or being started to this container, but haven't been * added to {@link #mInfo} yet. */ private final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>(); @VisibleForTesting final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>(); /** * When this container is created for an {@link Intent} to start within, we store that Intent * until the container becomes non-empty on the server side, so that we can use it to check * rules associated with this container. */ @Nullable private Intent mPendingAppearedIntent; /** Containers that are dependent on this one and should be completely destroyed on exit. */ private final List<TaskFragmentContainer> mContainersToFinishOnExit = Loading Loading @@ -99,15 +109,22 @@ class TaskFragmentContainer { * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. */ TaskFragmentContainer(@Nullable Activity activity, @NonNull TaskContainer taskContainer, TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller) { if ((pendingAppearedActivity == null && pendingAppearedIntent == null) || (pendingAppearedActivity != null && pendingAppearedIntent != null)) { throw new IllegalArgumentException( "One and only one of pending activity and intent must be non-null"); } mController = controller; mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; taskContainer.mContainers.add(this); if (activity != null) { addPendingAppearedActivity(activity); if (pendingAppearedActivity != null) { addPendingAppearedActivity(pendingAppearedActivity); } mPendingAppearedIntent = pendingAppearedIntent; } /** Loading @@ -118,9 +135,9 @@ class TaskFragmentContainer { return mToken; } /** List of activities that belong to this container and live in this process. */ /** List of non-finishing activities that belong to this container and live in this process. */ @NonNull List<Activity> collectActivities() { List<Activity> collectNonFinishingActivities() { final List<Activity> allActivities = new ArrayList<>(); if (mInfo != null) { // Add activities reported from the server. Loading Loading @@ -154,13 +171,14 @@ class TaskFragmentContainer { return false; } return mPendingAppearedActivities.isEmpty() && mInfo.getActivities().size() == collectActivities().size(); && mInfo.getActivities().size() == collectNonFinishingActivities().size(); } ActivityStack toActivityStack() { return new ActivityStack(collectActivities(), isEmpty()); return new ActivityStack(collectNonFinishingActivities(), isEmpty()); } /** Adds the activity that will be reparented to this container. */ void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { if (hasActivity(pendingAppearedActivity.getActivityToken())) { return; Loading @@ -174,6 +192,11 @@ class TaskFragmentContainer { mPendingAppearedActivities.remove(pendingAppearedActivity); } @Nullable Intent getPendingAppearedIntent() { return mPendingAppearedIntent; } boolean hasActivity(@NonNull IBinder token) { if (mInfo != null && mInfo.getActivities().contains(token)) { return true; Loading Loading @@ -219,7 +242,12 @@ class TaskFragmentContainer { } mInfo = info; if (mInfo == null || mPendingAppearedActivities.isEmpty()) { if (mInfo == null || mInfo.isEmpty()) { return; } // Only track the pending Intent when the container is empty. mPendingAppearedIntent = null; if (mPendingAppearedActivities.isEmpty()) { return; } // Cleanup activities that were being re-parented Loading @@ -234,20 +262,13 @@ class TaskFragmentContainer { @Nullable Activity getTopNonFinishingActivity() { List<Activity> activities = collectActivities(); if (activities.isEmpty()) { return null; } int i = activities.size() - 1; while (i >= 0 && activities.get(i).isFinishing()) { i--; } return i >= 0 ? activities.get(i) : null; final List<Activity> activities = collectNonFinishingActivities(); return activities.isEmpty() ? null : activities.get(activities.size() - 1); } @Nullable Activity getBottomMostActivity() { final List<Activity> activities = collectActivities(); final List<Activity> activities = collectNonFinishingActivities(); return activities.isEmpty() ? null : activities.get(0); } Loading Loading @@ -320,8 +341,11 @@ class TaskFragmentContainer { private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { // Finish own activities for (Activity activity : collectActivities()) { if (!activity.isFinishing()) { for (Activity activity : collectNonFinishingActivities()) { if (!activity.isFinishing() // In case we have requested to reparent the activity to another container (as // pendingAppeared), we don't want to finish it with this container. && mController.getContainerWithActivity(activity) == this) { activity.finish(); } } Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; import android.os.Handler; Loading Loading @@ -115,7 +116,7 @@ public class JetpackTaskFragmentOrganizerTest { public void testExpandTaskFragment() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, taskContainer, mSplitController); new Intent(), taskContainer, mSplitController); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(info); Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +34 −8 Original line number Diff line number Diff line Loading @@ -123,7 +123,7 @@ public class SplitControllerTest { final TaskContainer taskContainer = new TaskContainer(TASK_ID); // tf1 has no running activity so is not active. final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, taskContainer, mSplitController); new Intent(), taskContainer, mSplitController); // tf2 has running activity so is active. final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); Loading Loading @@ -205,7 +205,8 @@ public class SplitControllerTest { assertThrows(IllegalArgumentException.class, () -> mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID); final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, mActivity, TASK_ID); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertNotNull(tf); Loading Loading @@ -307,7 +308,7 @@ public class SplitControllerTest { @Test public void testOnActivityReparentToTask_diffProcess() { // Create an empty TaskFragment to initialize for the Task. mSplitController.newContainer(null, mActivity, TASK_ID); mSplitController.newContainer(new Intent(), mActivity, TASK_ID); final IBinder activityToken = new Binder(); final Intent intent = new Intent(); Loading Loading @@ -417,7 +418,7 @@ public class SplitControllerTest { verify(mSplitPresenter, never()).applyTransaction(any()); mSplitController.newContainer(null /* activity */, mActivity, TASK_ID); mSplitController.newContainer(new Intent(), mActivity, TASK_ID); mSplitController.placeActivityInTopContainer(mActivity); verify(mSplitPresenter).applyTransaction(any()); Loading @@ -436,7 +437,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertFalse(result); verify(mSplitController, never()).newContainer(any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); } @Test Loading Loading @@ -577,7 +578,7 @@ public class SplitControllerTest { final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, TASK_ID); final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( null /* activity */, mActivity, TASK_ID); secondaryIntent, mActivity, TASK_ID); mSplitController.registerSplit( mTransaction, primaryContainer, Loading @@ -589,10 +590,35 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); } @Test public void testResolveActivityToContainer_splitRule_inPrimarySplitWithNoRuleMatched() { final Intent secondaryIntent = new Intent(); setupSplitRule(mActivity, secondaryIntent); final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0); // The new launched activity is in primary split, but there is no rule for it to split with // the secondary, so return false. final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, TASK_ID); final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( secondaryIntent, mActivity, TASK_ID); mSplitController.registerSplit( mTransaction, primaryContainer, mActivity, secondaryContainer, splitRule); final Activity launchedActivity = createMockActivity(); primaryContainer.addPendingAppearedActivity(launchedActivity); assertFalse(mSplitController.resolveActivityToContainer(launchedActivity, false /* isOnReparent */)); } @Test public void testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched() { final Activity primaryActivity = createMockActivity(); Loading @@ -605,7 +631,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); } Loading Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +40 −19 Original line number Diff line number Diff line Loading @@ -530,11 +530,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == splitContainer.getPrimaryContainer()) { // The new launched can be in the primary container when it is starting a new activity // onCreate, thus the secondary may still be empty. // onCreate. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent(); if (secondaryIntent != null) { // Check with the pending Intent before it is started on the server side. // This can happen if the launched Activity start a new Intent to secondary during // #onCreated(). return getSplitRule(launchedActivity, secondaryIntent) != null; } final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity(); return secondaryActivity == null || getSplitRule(launchedActivity, secondaryActivity) != null; return secondaryActivity != null && getSplitRule(launchedActivity, secondaryActivity) != null; } // Check if the new launched activity is a placeholder. Loading Loading @@ -573,7 +580,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen Activity activityBelow = null; final TaskFragmentContainer container = getContainerWithActivity(activity); if (container != null) { final List<Activity> containerActivities = container.collectActivities(); final List<Activity> containerActivities = container.collectNonFinishingActivities(); final int index = containerActivities.indexOf(activity); if (index > 0) { activityBelow = containerActivities.get(index - 1); Loading Loading @@ -691,7 +698,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // 1. Whether the new activity intent should always expand. if (shouldExpand(null /* activity */, intent)) { return createEmptyExpandedContainer(wct, taskId, launchingActivity); return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity); } // 2. Whether the launching activity (if set) should be split with the new activity intent. Loading Loading @@ -742,7 +749,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Nullable private TaskFragmentContainer createEmptyExpandedContainer( @NonNull WindowContainerTransaction wct, int taskId, @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity) { // We need an activity in the organizer process in the same Task to use as the owner // activity, as well as to get the Task window info. Loading @@ -759,8 +766,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find any activity in the Task that we can use as the owner activity. return null; } final TaskFragmentContainer expandedContainer = newContainer(null /* activity */, activityInTask, taskId); final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask, taskId); mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(), activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); return expandedContainer; Loading Loading @@ -789,7 +796,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return splitContainer.getSecondaryContainer(); } // Create a new TaskFragment to split with the primary activity for the new activity. return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, splitRule); return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, splitRule); } /** Loading @@ -813,21 +821,34 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) { return newContainer(activity, activity, taskId); TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) { return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); } TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId) { return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, activityInTask, taskId); } TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, activityInTask, taskId); } /** * Creates and registers a new organized container with an optional activity that will be * re-parented to it in a WCT. * * @param activity the activity that will be reparented to the TaskFragment. * @param activityInTask activity in the same Task so that we can get the Task bounds if * needed. * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. * @param activityInTask activity in the same Task so that we can get the Task bounds * if needed. * @param taskId parent Task of the new TaskFragment. */ TaskFragmentContainer newContainer(@Nullable Activity activity, @NonNull Activity activityInTask, int taskId) { TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } Loading @@ -835,8 +856,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mTaskContainers.put(taskId, new TaskContainer(taskId)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskContainer, this); final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, pendingAppearedIntent, taskContainer, this); if (!taskContainer.isTaskBoundsInitialized()) { // Get the initial bounds before the TaskFragment has appeared. final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +3 −3 Original line number Diff line number Diff line Loading @@ -101,7 +101,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull TaskFragmentContainer createNewSplitWithEmptySideContainer( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull SplitPairRule rule) { @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { final Rect parentBounds = getParentContainerBounds(primaryActivity); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, isLtr(primaryActivity, rule)); Loading @@ -111,7 +111,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( null /* activity */, primaryActivity, taskId); secondaryIntent, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); final int windowingMode = mController.getTaskContainer(taskId) Loading Loading @@ -224,7 +224,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } final int taskId = primaryContainer.getTaskId(); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent, launchingActivity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +45 −21 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; Loading Loading @@ -64,7 +65,16 @@ class TaskFragmentContainer { * Activities that are being reparented or being started to this container, but haven't been * added to {@link #mInfo} yet. */ private final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>(); @VisibleForTesting final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>(); /** * When this container is created for an {@link Intent} to start within, we store that Intent * until the container becomes non-empty on the server side, so that we can use it to check * rules associated with this container. */ @Nullable private Intent mPendingAppearedIntent; /** Containers that are dependent on this one and should be completely destroyed on exit. */ private final List<TaskFragmentContainer> mContainersToFinishOnExit = Loading Loading @@ -99,15 +109,22 @@ class TaskFragmentContainer { * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. */ TaskFragmentContainer(@Nullable Activity activity, @NonNull TaskContainer taskContainer, TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller) { if ((pendingAppearedActivity == null && pendingAppearedIntent == null) || (pendingAppearedActivity != null && pendingAppearedIntent != null)) { throw new IllegalArgumentException( "One and only one of pending activity and intent must be non-null"); } mController = controller; mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; taskContainer.mContainers.add(this); if (activity != null) { addPendingAppearedActivity(activity); if (pendingAppearedActivity != null) { addPendingAppearedActivity(pendingAppearedActivity); } mPendingAppearedIntent = pendingAppearedIntent; } /** Loading @@ -118,9 +135,9 @@ class TaskFragmentContainer { return mToken; } /** List of activities that belong to this container and live in this process. */ /** List of non-finishing activities that belong to this container and live in this process. */ @NonNull List<Activity> collectActivities() { List<Activity> collectNonFinishingActivities() { final List<Activity> allActivities = new ArrayList<>(); if (mInfo != null) { // Add activities reported from the server. Loading Loading @@ -154,13 +171,14 @@ class TaskFragmentContainer { return false; } return mPendingAppearedActivities.isEmpty() && mInfo.getActivities().size() == collectActivities().size(); && mInfo.getActivities().size() == collectNonFinishingActivities().size(); } ActivityStack toActivityStack() { return new ActivityStack(collectActivities(), isEmpty()); return new ActivityStack(collectNonFinishingActivities(), isEmpty()); } /** Adds the activity that will be reparented to this container. */ void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { if (hasActivity(pendingAppearedActivity.getActivityToken())) { return; Loading @@ -174,6 +192,11 @@ class TaskFragmentContainer { mPendingAppearedActivities.remove(pendingAppearedActivity); } @Nullable Intent getPendingAppearedIntent() { return mPendingAppearedIntent; } boolean hasActivity(@NonNull IBinder token) { if (mInfo != null && mInfo.getActivities().contains(token)) { return true; Loading Loading @@ -219,7 +242,12 @@ class TaskFragmentContainer { } mInfo = info; if (mInfo == null || mPendingAppearedActivities.isEmpty()) { if (mInfo == null || mInfo.isEmpty()) { return; } // Only track the pending Intent when the container is empty. mPendingAppearedIntent = null; if (mPendingAppearedActivities.isEmpty()) { return; } // Cleanup activities that were being re-parented Loading @@ -234,20 +262,13 @@ class TaskFragmentContainer { @Nullable Activity getTopNonFinishingActivity() { List<Activity> activities = collectActivities(); if (activities.isEmpty()) { return null; } int i = activities.size() - 1; while (i >= 0 && activities.get(i).isFinishing()) { i--; } return i >= 0 ? activities.get(i) : null; final List<Activity> activities = collectNonFinishingActivities(); return activities.isEmpty() ? null : activities.get(activities.size() - 1); } @Nullable Activity getBottomMostActivity() { final List<Activity> activities = collectActivities(); final List<Activity> activities = collectNonFinishingActivities(); return activities.isEmpty() ? null : activities.get(0); } Loading Loading @@ -320,8 +341,11 @@ class TaskFragmentContainer { private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { // Finish own activities for (Activity activity : collectActivities()) { if (!activity.isFinishing()) { for (Activity activity : collectNonFinishingActivities()) { if (!activity.isFinishing() // In case we have requested to reparent the activity to another container (as // pendingAppeared), we don't want to finish it with this container. && mController.getContainerWithActivity(activity) == this) { activity.finish(); } } Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; import android.os.Handler; Loading Loading @@ -115,7 +116,7 @@ public class JetpackTaskFragmentOrganizerTest { public void testExpandTaskFragment() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, taskContainer, mSplitController); new Intent(), taskContainer, mSplitController); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(info); Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +34 −8 Original line number Diff line number Diff line Loading @@ -123,7 +123,7 @@ public class SplitControllerTest { final TaskContainer taskContainer = new TaskContainer(TASK_ID); // tf1 has no running activity so is not active. final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, taskContainer, mSplitController); new Intent(), taskContainer, mSplitController); // tf2 has running activity so is active. final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); Loading Loading @@ -205,7 +205,8 @@ public class SplitControllerTest { assertThrows(IllegalArgumentException.class, () -> mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID); final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, mActivity, TASK_ID); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertNotNull(tf); Loading Loading @@ -307,7 +308,7 @@ public class SplitControllerTest { @Test public void testOnActivityReparentToTask_diffProcess() { // Create an empty TaskFragment to initialize for the Task. mSplitController.newContainer(null, mActivity, TASK_ID); mSplitController.newContainer(new Intent(), mActivity, TASK_ID); final IBinder activityToken = new Binder(); final Intent intent = new Intent(); Loading Loading @@ -417,7 +418,7 @@ public class SplitControllerTest { verify(mSplitPresenter, never()).applyTransaction(any()); mSplitController.newContainer(null /* activity */, mActivity, TASK_ID); mSplitController.newContainer(new Intent(), mActivity, TASK_ID); mSplitController.placeActivityInTopContainer(mActivity); verify(mSplitPresenter).applyTransaction(any()); Loading @@ -436,7 +437,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertFalse(result); verify(mSplitController, never()).newContainer(any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); } @Test Loading Loading @@ -577,7 +578,7 @@ public class SplitControllerTest { final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, TASK_ID); final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( null /* activity */, mActivity, TASK_ID); secondaryIntent, mActivity, TASK_ID); mSplitController.registerSplit( mTransaction, primaryContainer, Loading @@ -589,10 +590,35 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); } @Test public void testResolveActivityToContainer_splitRule_inPrimarySplitWithNoRuleMatched() { final Intent secondaryIntent = new Intent(); setupSplitRule(mActivity, secondaryIntent); final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0); // The new launched activity is in primary split, but there is no rule for it to split with // the secondary, so return false. final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, TASK_ID); final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( secondaryIntent, mActivity, TASK_ID); mSplitController.registerSplit( mTransaction, primaryContainer, mActivity, secondaryContainer, splitRule); final Activity launchedActivity = createMockActivity(); primaryContainer.addPendingAppearedActivity(launchedActivity); assertFalse(mSplitController.resolveActivityToContainer(launchedActivity, false /* isOnReparent */)); } @Test public void testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched() { final Activity primaryActivity = createMockActivity(); Loading @@ -605,7 +631,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); } Loading