diff options
6 files changed, 196 insertions, 29 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index e50b9a1cd469..81caf7786cf5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -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; @@ -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 @@ -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. @@ -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); } /** @@ -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( diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 2328f76a7130..b370e59ac7c8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -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; @@ -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; } @@ -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. @@ -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; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index ee5a322eed4f..e64e5d1c66d5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -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; @@ -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. @@ -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; @@ -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(); + final int windowingMode = mController.getTaskContainer(taskId) + .getWindowingModeForSplitTaskFragment(bounds); if (container == null || container == containerToAvoid) { - container = mController.newContainer(activity, activity.getTaskId()); - + container = mController.newContainer(activity, taskId); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( container.getTaskFragmentToken(), activity.getActivityToken(), bounds, - WINDOWING_MODE_MULTI_WINDOW); + windowingMode); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), @@ -206,6 +212,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); + updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } return container; @@ -237,14 +244,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()); @@ -292,6 +302,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, @@ -323,6 +339,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) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index be793018d969..3c0762d81494 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -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; @@ -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<>(); @@ -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. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index b06ce4c19d5c..1f12c4484159 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -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; @@ -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}. * @@ -48,6 +58,8 @@ public class JetpackTaskFragmentOrganizerTest { private static final int TASK_ID = 10; @Mock + private WindowContainerTransaction mTransaction; + @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; private JetpackTaskFragmentOrganizer mOrganizer; @@ -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 */); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 9fb08dffbab8..c7feb7e59de3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -16,6 +16,13 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -64,6 +71,56 @@ public class TaskContainerTest { } @Test + public void testIsWindowingModeInitialized() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + + assertFalse(taskContainer.isWindowingModeInitialized()); + + taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertTrue(taskContainer.isWindowingModeInitialized()); + } + + @Test + public void testGetWindowingModeForSplitTaskFragment() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final Rect splitBounds = new Rect(0, 0, 500, 1000); + + assertEquals(WINDOWING_MODE_MULTI_WINDOW, + taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + + taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertEquals(WINDOWING_MODE_MULTI_WINDOW, + taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + + taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM); + + assertEquals(WINDOWING_MODE_FREEFORM, + taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + + // Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then + // inherit the Task windowing mode + assertEquals(WINDOWING_MODE_UNDEFINED, + taskContainer.getWindowingModeForSplitTaskFragment(new Rect())); + } + + @Test + public void testIsInPictureInPicture() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + + assertFalse(taskContainer.isInPictureInPicture()); + + taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertFalse(taskContainer.isInPictureInPicture()); + + taskContainer.setWindowingMode(WINDOWING_MODE_PINNED); + + assertTrue(taskContainer.isInPictureInPicture()); + } + + @Test public void testIsEmpty() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); |