Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +19 −5 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; Loading Loading @@ -49,7 +48,8 @@ import java.util.concurrent.Executor; class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); @VisibleForTesting final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); /** * Mapping from the client assigned unique token to the TaskFragment parent Loading Loading @@ -120,25 +120,29 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment * @param activityIntent Intent to start the secondary Activity with. * @param activityOptions ActivityOptions to start the secondary Activity with. * @param windowingMode the windowing mode to set for the TaskFragments. */ void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule) { @Nullable Bundle activityOptions, @NonNull SplitRule rule, @WindowingMode int windowingMode) { final IBinder ownerToken = launchingActivity.getActivityToken(); // Create or resize the launching TaskFragment. if (mFragmentInfos.containsKey(launchingFragmentToken)) { resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds); wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(), windowingMode); } else { createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity); launchingFragmentBounds, windowingMode, launchingActivity); } // Create a TaskFragment for the secondary activity. createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken, secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent, secondaryFragmentBounds, windowingMode, activityIntent, activityOptions); // Set adjacent to each other so that the containers below will be invisible. Loading @@ -153,6 +157,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { resizeTaskFragment(wct, fragmentToken, new Rect()); setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); setWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); } /** Loading Loading @@ -255,6 +260,15 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds); } private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken, @WindowingMode int windowingMode) { if (!mFragmentInfos.containsKey(fragmentToken)) { throw new IllegalArgumentException( "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); } wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode); } void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { if (!mFragmentInfos.containsKey(fragmentToken)) { throw new IllegalArgumentException( Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +11 −6 Original line number Diff line number Diff line Loading @@ -257,9 +257,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration()); final boolean wasInPip = taskContainer.isInPictureInPicture(); final boolean isInPIp = isInPictureInPicture(config); taskContainer.setConfiguration(config); taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode()); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; Loading @@ -278,8 +278,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * bounds is large enough for at least one split rule. */ private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { if (!taskContainer.isTaskBoundsInitialized()) { // We don't know about the Task bounds yet. if (!taskContainer.isTaskBoundsInitialized() || !taskContainer.isWindowingModeInitialized()) { // We don't know about the Task bounds/windowingMode yet. return; } Loading @@ -293,7 +294,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private boolean supportSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. if (isInPictureInPicture(taskContainer.getConfiguration())) { if (taskContainer.isInPictureInPicture()) { return false; } // Check if the parent container bounds can support any split rule. Loading Loading @@ -461,8 +462,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } updateAnimationOverride(taskContainer); } if (!taskContainer.isWindowingModeInitialized()) { taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration() .windowConfiguration.getWindowingMode()); } updateAnimationOverride(taskContainer); return container; } Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +36 −9 Original line number Diff line number Diff line Loading @@ -16,10 +16,11 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.Intent; import android.graphics.Rect; Loading Loading @@ -111,13 +112,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( null /* activity */, primaryActivity, primaryContainer.getTaskId()); null /* activity */, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, WINDOWING_MODE_MULTI_WINDOW); windowingMode); secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. Loading Loading @@ -173,7 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW); launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); applyTransaction(wct); return newContainer; Loading @@ -189,15 +193,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) { TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); if (container == null || container == containerToAvoid) { container = mController.newContainer(activity, activity.getTaskId()); container = mController.newContainer(activity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(bounds); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( container.getTaskFragmentToken(), activity.getActivityToken(), bounds, WINDOWING_MODE_MULTI_WINDOW); windowingMode); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), Loading @@ -206,6 +212,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(bounds); updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } return container; Loading Loading @@ -237,14 +246,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity.getTaskId()); } final int taskId = primaryContainer.getTaskId(); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, launchingActivity, primaryContainer.getTaskId()); launchingActivity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, activityIntent, activityOptions, rule); activityIntent, activityOptions, rule, windowingMode); if (isPlaceholder) { // When placeholder is launched in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); Loading Loading @@ -292,6 +304,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } final TaskContainer taskContainer = mController.getTaskContainer( updatedContainer.getTaskId()); final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( primaryRectBounds); updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, Loading Loading @@ -323,6 +341,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds); } private void updateTaskFragmentWindowingModeIfRegistered( @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @WindowingMode int windowingMode) { if (container.getInfo() != null) { wct.setWindowingMode(container.getInfo().getToken(), windowingMode); } } @Override void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds) { Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +43 −9 Original line number Diff line number Diff line Loading @@ -16,9 +16,14 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; Loading @@ -37,9 +42,9 @@ class TaskContainer { /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); /** Configuration of the Task. */ @Nullable private Configuration mConfiguration; /** Windowing mode of this Task. */ @WindowingMode private int mWindowingMode = WINDOWING_MODE_UNDEFINED; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); Loading Loading @@ -81,13 +86,42 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } @Nullable Configuration getConfiguration() { return mConfiguration; void setWindowingMode(int windowingMode) { mWindowingMode = windowingMode; } /** Whether the Task windowing mode has been initialized. */ boolean isWindowingModeInitialized() { return mWindowingMode != WINDOWING_MODE_UNDEFINED; } /** * Returns the windowing mode for the TaskFragments below this Task, which should be split with * other TaskFragments. * * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when * the pair of TaskFragments are stacked due to the limited space. */ @WindowingMode int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) { // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it // will be set to UNDEFINED which will then inherit the Task windowing mode. if (taskFragmentBounds == null || taskFragmentBounds.isEmpty()) { return WINDOWING_MODE_UNDEFINED; } // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. // However, when the Task is in other multi windowing mode, such as Freeform, we need to // have the activity windowing mode to match the Task, otherwise things like // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the // Task windowing mode if the Task is in multi window. // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. return WindowConfiguration.inMultiWindowMode(mWindowingMode) ? mWindowingMode : WINDOWING_MODE_MULTI_WINDOW; } void setConfiguration(@Nullable Configuration configuration) { mConfiguration = configuration; boolean isInPictureInPicture() { return mWindowingMode == WINDOWING_MODE_PINNED; } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -16,15 +16,23 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.content.res.Configuration; import android.graphics.Point; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; Loading @@ -35,6 +43,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; /** * Test class for {@link JetpackTaskFragmentOrganizer}. * Loading @@ -47,6 +57,8 @@ import org.mockito.MockitoAnnotations; public class JetpackTaskFragmentOrganizerTest { private static final int TASK_ID = 10; @Mock private WindowContainerTransaction mTransaction; @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; private JetpackTaskFragmentOrganizer mOrganizer; Loading Loading @@ -91,4 +103,24 @@ public class JetpackTaskFragmentOrganizerTest { verify(mOrganizer).unregisterRemoteAnimations(TASK_ID); } @Test public void testExpandTaskFragment() { final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(info); mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken()); verify(mTransaction).setWindowingMode(container.getInfo().getToken(), WINDOWING_MODE_UNDEFINED); } private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, false /* isVisible */, new ArrayList<>(), new Point(), false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */); } } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +19 −5 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; Loading Loading @@ -49,7 +48,8 @@ import java.util.concurrent.Executor; class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); @VisibleForTesting final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); /** * Mapping from the client assigned unique token to the TaskFragment parent Loading Loading @@ -120,25 +120,29 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment * @param activityIntent Intent to start the secondary Activity with. * @param activityOptions ActivityOptions to start the secondary Activity with. * @param windowingMode the windowing mode to set for the TaskFragments. */ void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule) { @Nullable Bundle activityOptions, @NonNull SplitRule rule, @WindowingMode int windowingMode) { final IBinder ownerToken = launchingActivity.getActivityToken(); // Create or resize the launching TaskFragment. if (mFragmentInfos.containsKey(launchingFragmentToken)) { resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds); wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(), windowingMode); } else { createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity); launchingFragmentBounds, windowingMode, launchingActivity); } // Create a TaskFragment for the secondary activity. createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken, secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent, secondaryFragmentBounds, windowingMode, activityIntent, activityOptions); // Set adjacent to each other so that the containers below will be invisible. Loading @@ -153,6 +157,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { resizeTaskFragment(wct, fragmentToken, new Rect()); setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); setWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); } /** Loading Loading @@ -255,6 +260,15 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds); } private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken, @WindowingMode int windowingMode) { if (!mFragmentInfos.containsKey(fragmentToken)) { throw new IllegalArgumentException( "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); } wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode); } void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { if (!mFragmentInfos.containsKey(fragmentToken)) { throw new IllegalArgumentException( Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +11 −6 Original line number Diff line number Diff line Loading @@ -257,9 +257,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration()); final boolean wasInPip = taskContainer.isInPictureInPicture(); final boolean isInPIp = isInPictureInPicture(config); taskContainer.setConfiguration(config); taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode()); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; Loading @@ -278,8 +278,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * bounds is large enough for at least one split rule. */ private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { if (!taskContainer.isTaskBoundsInitialized()) { // We don't know about the Task bounds yet. if (!taskContainer.isTaskBoundsInitialized() || !taskContainer.isWindowingModeInitialized()) { // We don't know about the Task bounds/windowingMode yet. return; } Loading @@ -293,7 +294,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private boolean supportSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. if (isInPictureInPicture(taskContainer.getConfiguration())) { if (taskContainer.isInPictureInPicture()) { return false; } // Check if the parent container bounds can support any split rule. Loading Loading @@ -461,8 +462,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } updateAnimationOverride(taskContainer); } if (!taskContainer.isWindowingModeInitialized()) { taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration() .windowConfiguration.getWindowingMode()); } updateAnimationOverride(taskContainer); return container; } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +36 −9 Original line number Diff line number Diff line Loading @@ -16,10 +16,11 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.Intent; import android.graphics.Rect; Loading Loading @@ -111,13 +112,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( null /* activity */, primaryActivity, primaryContainer.getTaskId()); null /* activity */, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, WINDOWING_MODE_MULTI_WINDOW); windowingMode); secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. Loading Loading @@ -173,7 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW); launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); applyTransaction(wct); return newContainer; Loading @@ -189,15 +193,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) { TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); if (container == null || container == containerToAvoid) { container = mController.newContainer(activity, activity.getTaskId()); container = mController.newContainer(activity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(bounds); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( container.getTaskFragmentToken(), activity.getActivityToken(), bounds, WINDOWING_MODE_MULTI_WINDOW); windowingMode); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), Loading @@ -206,6 +212,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(bounds); updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } return container; Loading Loading @@ -237,14 +246,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity.getTaskId()); } final int taskId = primaryContainer.getTaskId(); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, launchingActivity, primaryContainer.getTaskId()); launchingActivity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, activityIntent, activityOptions, rule); activityIntent, activityOptions, rule, windowingMode); if (isPlaceholder) { // When placeholder is launched in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); Loading Loading @@ -292,6 +304,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } final TaskContainer taskContainer = mController.getTaskContainer( updatedContainer.getTaskId()); final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( primaryRectBounds); updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, Loading Loading @@ -323,6 +341,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds); } private void updateTaskFragmentWindowingModeIfRegistered( @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @WindowingMode int windowingMode) { if (container.getInfo() != null) { wct.setWindowingMode(container.getInfo().getToken(), windowingMode); } } @Override void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds) { Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +43 −9 Original line number Diff line number Diff line Loading @@ -16,9 +16,14 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; Loading @@ -37,9 +42,9 @@ class TaskContainer { /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); /** Configuration of the Task. */ @Nullable private Configuration mConfiguration; /** Windowing mode of this Task. */ @WindowingMode private int mWindowingMode = WINDOWING_MODE_UNDEFINED; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); Loading Loading @@ -81,13 +86,42 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } @Nullable Configuration getConfiguration() { return mConfiguration; void setWindowingMode(int windowingMode) { mWindowingMode = windowingMode; } /** Whether the Task windowing mode has been initialized. */ boolean isWindowingModeInitialized() { return mWindowingMode != WINDOWING_MODE_UNDEFINED; } /** * Returns the windowing mode for the TaskFragments below this Task, which should be split with * other TaskFragments. * * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when * the pair of TaskFragments are stacked due to the limited space. */ @WindowingMode int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) { // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it // will be set to UNDEFINED which will then inherit the Task windowing mode. if (taskFragmentBounds == null || taskFragmentBounds.isEmpty()) { return WINDOWING_MODE_UNDEFINED; } // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. // However, when the Task is in other multi windowing mode, such as Freeform, we need to // have the activity windowing mode to match the Task, otherwise things like // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the // Task windowing mode if the Task is in multi window. // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. return WindowConfiguration.inMultiWindowMode(mWindowingMode) ? mWindowingMode : WINDOWING_MODE_MULTI_WINDOW; } void setConfiguration(@Nullable Configuration configuration) { mConfiguration = configuration; boolean isInPictureInPicture() { return mWindowingMode == WINDOWING_MODE_PINNED; } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -16,15 +16,23 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.content.res.Configuration; import android.graphics.Point; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; Loading @@ -35,6 +43,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; /** * Test class for {@link JetpackTaskFragmentOrganizer}. * Loading @@ -47,6 +57,8 @@ import org.mockito.MockitoAnnotations; public class JetpackTaskFragmentOrganizerTest { private static final int TASK_ID = 10; @Mock private WindowContainerTransaction mTransaction; @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; private JetpackTaskFragmentOrganizer mOrganizer; Loading Loading @@ -91,4 +103,24 @@ public class JetpackTaskFragmentOrganizerTest { verify(mOrganizer).unregisterRemoteAnimations(TASK_ID); } @Test public void testExpandTaskFragment() { final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(info); mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken()); verify(mTransaction).setWindowingMode(container.getInfo().getToken(), WINDOWING_MODE_UNDEFINED); } private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, false /* isVisible */, new ArrayList<>(), new Point(), false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */); } }