Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +36 −57 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; Loading @@ -59,6 +60,7 @@ import java.util.function.Consumer; */ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent { private static final String TAG = "SplitController"; @VisibleForTesting final SplitPresenter mPresenter; Loading Loading @@ -229,8 +231,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } if (taskContainer.isEmpty()) { // Cleanup the TaskContainer if it becomes empty. mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId); mTaskContainers.remove(taskContainer.mTaskId); mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); mTaskContainers.remove(taskContainer.getTaskId()); } return; } Loading @@ -241,13 +243,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } final boolean wasInPip = isInPictureInPicture(taskContainer.mConfiguration); final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration()); final boolean isInPIp = isInPictureInPicture(config); taskContainer.mConfiguration = config; taskContainer.setConfiguration(config); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; if (onTaskBoundsMayChange(taskContainer, config.windowConfiguration.getBounds()) if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds()) && !isInPIp) { // We don't care the bounds change when it has already entered PIP. shouldUpdateAnimationOverride = true; Loading @@ -257,16 +259,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } /** Returns {@code true} if the bounds is changed. */ private boolean onTaskBoundsMayChange(@NonNull TaskContainer taskContainer, @NonNull Rect taskBounds) { if (!taskBounds.isEmpty() && !taskContainer.mTaskBounds.equals(taskBounds)) { taskContainer.mTaskBounds.set(taskBounds); return true; } return false; } /** * Updates if we should override transition animation. We only want to override if the Task * bounds is large enough for at least one split rule. Loading @@ -279,15 +271,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // We only want to override if it supports split. if (supportSplit(taskContainer)) { mPresenter.startOverrideSplitAnimation(taskContainer.mTaskId); mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId()); } else { mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId); mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); } } private boolean supportSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. if (isInPictureInPicture(taskContainer.mConfiguration)) { if (isInPictureInPicture(taskContainer.getConfiguration())) { return false; } // Check if the parent container bounds can support any split rule. Loading @@ -295,7 +287,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!(rule instanceof SplitRule)) { continue; } if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) { if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { return true; } } Loading Loading @@ -425,21 +417,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) { return newContainer(activity, activity, 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 taskId parent Task of the new TaskFragment. */ TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) { TaskFragmentContainer newContainer(@Nullable Activity activity, @NonNull Activity activityInTask, int taskId) { if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId); if (!mTaskContainers.contains(taskId)) { mTaskContainers.put(taskId, new TaskContainer(taskId)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); taskContainer.mContainers.add(container); if (activity != null && !taskContainer.isTaskBoundsInitialized() && onTaskBoundsMayChange(taskContainer, SplitPresenter.getTaskBoundsFromActivity(activity))) { // Initial check before any TaskFragment has appeared. if (!taskContainer.isTaskBoundsInitialized()) { // Get the initial bounds before the TaskFragment has appeared. final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } updateAnimationOverride(taskContainer); } return container; Loading Loading @@ -887,6 +894,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } @Nullable TaskContainer getTaskContainer(int taskId) { return mTaskContainers.get(taskId); } /** * Returns {@code true} if an Activity with the provided component name should always be * expanded to occupy full task bounds. Such activity must not be put in a split. Loading Loading @@ -1211,37 +1223,4 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return configuration != null && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; } /** Represents TaskFragments and split pairs below a Task. */ @VisibleForTesting static class TaskContainer { /** The unique task id. */ final int mTaskId; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /** Active split pairs in this Task. */ final List<SplitContainer> mSplitContainers = new ArrayList<>(); /** * TaskFragments that the organizer has requested to be closed. They should be removed when * the organizer receives {@link #onTaskFragmentVanished(TaskFragmentInfo)} event for them. */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); /** Available window bounds of this Task. */ final Rect mTaskBounds = new Rect(); /** Configuration of the Task. */ @Nullable Configuration mConfiguration; TaskContainer(int taskId) { mTaskId = taskId; } boolean isEmpty() { return mContainers.isEmpty() && mFinishedContainer.isEmpty(); } boolean isTaskBoundsInitialized() { return !mTaskBounds.isEmpty(); } } } libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +17 −28 Original line number Diff line number Diff line Loading @@ -19,9 +19,9 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.app.Activity; import android.app.WindowConfiguration; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; Loading Loading @@ -111,8 +111,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment final TaskFragmentContainer secondaryContainer = mController.newContainer(null, primaryContainer.getTaskId()); final TaskFragmentContainer secondaryContainer = mController.newContainer( null /* activity */, primaryActivity, primaryContainer.getTaskId()); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), Loading Loading @@ -168,8 +168,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Creates a new expanded container. */ TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) { final TaskFragmentContainer newContainer = mController.newContainer(null, launchingActivity.getTaskId()); final TaskFragmentContainer newContainer = mController.newContainer(null /* activity */, launchingActivity, launchingActivity.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), Loading Loading @@ -236,8 +236,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity.getTaskId()); } TaskFragmentContainer secondaryContainer = mController.newContainer(null, primaryContainer.getTaskId()); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, launchingActivity, primaryContainer.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); Loading Loading @@ -398,20 +398,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { final Configuration parentConfig = mFragmentParentConfigs.get( container.getTaskFragmentToken()); if (parentConfig != null) { return parentConfig.windowConfiguration.getBounds(); } // If there is no parent yet - then assuming that activities are running in full task bounds final Activity topActivity = container.getTopNonFinishingActivity(); final Rect bounds = topActivity != null ? getParentContainerBounds(topActivity) : null; if (bounds == null) { throw new IllegalStateException("Unknown parent bounds"); final int taskId = container.getTaskId(); final TaskContainer taskContainer = mController.getTaskContainer(taskId); if (taskContainer == null) { throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId); } return bounds; return taskContainer.getTaskBounds(); } @NonNull Loading @@ -419,22 +411,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); if (container != null) { final Configuration parentConfig = mFragmentParentConfigs.get( container.getTaskFragmentToken()); if (parentConfig != null) { return parentConfig.windowConfiguration.getBounds(); } return getParentContainerBounds(container); } return getTaskBoundsFromActivity(activity); } @NonNull static Rect getTaskBoundsFromActivity(@NonNull Activity activity) { final WindowConfiguration windowConfiguration = activity.getResources().getConfiguration().windowConfiguration; if (!activity.isInMultiWindowMode()) { // In fullscreen mode the max bounds should correspond to the task bounds. return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds(); return windowConfiguration.getMaxBounds(); } return activity.getResources().getConfiguration().windowConfiguration.getBounds(); return windowConfiguration.getBounds(); } } libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; import android.window.TaskFragmentInfo; import java.util.ArrayList; import java.util.List; import java.util.Set; /** Represents TaskFragments and split pairs below a Task. */ class TaskContainer { /** The unique task id. */ private final int mTaskId; /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); /** Configuration of the Task. */ @Nullable private Configuration mConfiguration; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /** Active split pairs in this Task. */ final List<SplitContainer> mSplitContainers = new ArrayList<>(); /** * TaskFragments that the organizer has requested to be closed. They should be removed when * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event * for them. */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); TaskContainer(int taskId) { mTaskId = taskId; } int getTaskId() { return mTaskId; } @NonNull Rect getTaskBounds() { return mTaskBounds; } /** Returns {@code true} if the bounds is changed. */ boolean setTaskBounds(@NonNull Rect taskBounds) { if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) { mTaskBounds.set(taskBounds); return true; } return false; } /** Whether the Task bounds has been initialized. */ boolean isTaskBoundsInitialized() { return !mTaskBounds.isEmpty(); } @Nullable Configuration getConfiguration() { return mConfiguration; } void setConfiguration(@Nullable Configuration configuration) { mConfiguration = configuration; } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ boolean isEmpty() { return mContainers.isEmpty() && mFinishedContainer.isEmpty(); } } libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +25 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; Loading @@ -29,12 +32,12 @@ import static org.mockito.Mockito.never; import android.app.Activity; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.window.extensions.embedding.SplitController.TaskContainer; import org.junit.Before; import org.junit.Test; Loading @@ -53,6 +56,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class SplitControllerTest { private static final int TASK_ID = 10; private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); @Mock private Activity mActivity; Loading @@ -70,8 +74,11 @@ public class SplitControllerTest { mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); spyOn(mSplitPresenter); final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); doReturn(mActivityResources).when(mActivity).getResources(); doReturn(new Configuration()).when(mActivityResources).getConfiguration(); doReturn(activityConfig).when(mActivityResources).getConfiguration(); } @Test Loading Loading @@ -117,4 +124,20 @@ public class SplitControllerTest { verify(mSplitController).removeContainer(tf); verify(mActivity, never()).finish(); } @Test public void testNewContainer() { // Must pass in a valid activity. assertThrows(IllegalArgumentException.class, () -> mSplitController.newContainer(null /* activity */, TASK_ID)); assertThrows(IllegalArgumentException.class, () -> mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertNotNull(tf); assertNotNull(taskContainer); assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds()); } } libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.window.extensions.embedding; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; /** * Test class for {@link TaskContainer}. * * Build/Install/Run: * atest WMJetpackUnitTests:TaskContainerTest */ @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class TaskContainerTest { private static final int TASK_ID = 10; private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); @Test public void testIsTaskBoundsInitialized() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); assertFalse(taskContainer.isTaskBoundsInitialized()); taskContainer.setTaskBounds(TASK_BOUNDS); assertTrue(taskContainer.isTaskBoundsInitialized()); } @Test public void testSetTaskBounds() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); assertFalse(taskContainer.setTaskBounds(new Rect())); assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS)); assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS)); } @Test public void testIsEmpty() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); assertTrue(taskContainer.isEmpty()); final TaskFragmentContainer tf = new TaskFragmentContainer(null, TASK_ID); taskContainer.mContainers.add(tf); assertFalse(taskContainer.isEmpty()); taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken()); taskContainer.mContainers.clear(); assertFalse(taskContainer.isEmpty()); } } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +36 −57 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; Loading @@ -59,6 +60,7 @@ import java.util.function.Consumer; */ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent { private static final String TAG = "SplitController"; @VisibleForTesting final SplitPresenter mPresenter; Loading Loading @@ -229,8 +231,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } if (taskContainer.isEmpty()) { // Cleanup the TaskContainer if it becomes empty. mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId); mTaskContainers.remove(taskContainer.mTaskId); mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); mTaskContainers.remove(taskContainer.getTaskId()); } return; } Loading @@ -241,13 +243,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } final boolean wasInPip = isInPictureInPicture(taskContainer.mConfiguration); final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration()); final boolean isInPIp = isInPictureInPicture(config); taskContainer.mConfiguration = config; taskContainer.setConfiguration(config); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; if (onTaskBoundsMayChange(taskContainer, config.windowConfiguration.getBounds()) if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds()) && !isInPIp) { // We don't care the bounds change when it has already entered PIP. shouldUpdateAnimationOverride = true; Loading @@ -257,16 +259,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } /** Returns {@code true} if the bounds is changed. */ private boolean onTaskBoundsMayChange(@NonNull TaskContainer taskContainer, @NonNull Rect taskBounds) { if (!taskBounds.isEmpty() && !taskContainer.mTaskBounds.equals(taskBounds)) { taskContainer.mTaskBounds.set(taskBounds); return true; } return false; } /** * Updates if we should override transition animation. We only want to override if the Task * bounds is large enough for at least one split rule. Loading @@ -279,15 +271,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // We only want to override if it supports split. if (supportSplit(taskContainer)) { mPresenter.startOverrideSplitAnimation(taskContainer.mTaskId); mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId()); } else { mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId); mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); } } private boolean supportSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. if (isInPictureInPicture(taskContainer.mConfiguration)) { if (isInPictureInPicture(taskContainer.getConfiguration())) { return false; } // Check if the parent container bounds can support any split rule. Loading @@ -295,7 +287,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!(rule instanceof SplitRule)) { continue; } if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) { if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { return true; } } Loading Loading @@ -425,21 +417,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) { return newContainer(activity, activity, 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 taskId parent Task of the new TaskFragment. */ TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) { TaskFragmentContainer newContainer(@Nullable Activity activity, @NonNull Activity activityInTask, int taskId) { if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId); if (!mTaskContainers.contains(taskId)) { mTaskContainers.put(taskId, new TaskContainer(taskId)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); taskContainer.mContainers.add(container); if (activity != null && !taskContainer.isTaskBoundsInitialized() && onTaskBoundsMayChange(taskContainer, SplitPresenter.getTaskBoundsFromActivity(activity))) { // Initial check before any TaskFragment has appeared. if (!taskContainer.isTaskBoundsInitialized()) { // Get the initial bounds before the TaskFragment has appeared. final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } updateAnimationOverride(taskContainer); } return container; Loading Loading @@ -887,6 +894,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } @Nullable TaskContainer getTaskContainer(int taskId) { return mTaskContainers.get(taskId); } /** * Returns {@code true} if an Activity with the provided component name should always be * expanded to occupy full task bounds. Such activity must not be put in a split. Loading Loading @@ -1211,37 +1223,4 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return configuration != null && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; } /** Represents TaskFragments and split pairs below a Task. */ @VisibleForTesting static class TaskContainer { /** The unique task id. */ final int mTaskId; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /** Active split pairs in this Task. */ final List<SplitContainer> mSplitContainers = new ArrayList<>(); /** * TaskFragments that the organizer has requested to be closed. They should be removed when * the organizer receives {@link #onTaskFragmentVanished(TaskFragmentInfo)} event for them. */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); /** Available window bounds of this Task. */ final Rect mTaskBounds = new Rect(); /** Configuration of the Task. */ @Nullable Configuration mConfiguration; TaskContainer(int taskId) { mTaskId = taskId; } boolean isEmpty() { return mContainers.isEmpty() && mFinishedContainer.isEmpty(); } boolean isTaskBoundsInitialized() { return !mTaskBounds.isEmpty(); } } }
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +17 −28 Original line number Diff line number Diff line Loading @@ -19,9 +19,9 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.app.Activity; import android.app.WindowConfiguration; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; Loading Loading @@ -111,8 +111,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment final TaskFragmentContainer secondaryContainer = mController.newContainer(null, primaryContainer.getTaskId()); final TaskFragmentContainer secondaryContainer = mController.newContainer( null /* activity */, primaryActivity, primaryContainer.getTaskId()); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), Loading Loading @@ -168,8 +168,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Creates a new expanded container. */ TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) { final TaskFragmentContainer newContainer = mController.newContainer(null, launchingActivity.getTaskId()); final TaskFragmentContainer newContainer = mController.newContainer(null /* activity */, launchingActivity, launchingActivity.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), Loading Loading @@ -236,8 +236,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity.getTaskId()); } TaskFragmentContainer secondaryContainer = mController.newContainer(null, primaryContainer.getTaskId()); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, launchingActivity, primaryContainer.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); Loading Loading @@ -398,20 +398,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { final Configuration parentConfig = mFragmentParentConfigs.get( container.getTaskFragmentToken()); if (parentConfig != null) { return parentConfig.windowConfiguration.getBounds(); } // If there is no parent yet - then assuming that activities are running in full task bounds final Activity topActivity = container.getTopNonFinishingActivity(); final Rect bounds = topActivity != null ? getParentContainerBounds(topActivity) : null; if (bounds == null) { throw new IllegalStateException("Unknown parent bounds"); final int taskId = container.getTaskId(); final TaskContainer taskContainer = mController.getTaskContainer(taskId); if (taskContainer == null) { throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId); } return bounds; return taskContainer.getTaskBounds(); } @NonNull Loading @@ -419,22 +411,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); if (container != null) { final Configuration parentConfig = mFragmentParentConfigs.get( container.getTaskFragmentToken()); if (parentConfig != null) { return parentConfig.windowConfiguration.getBounds(); } return getParentContainerBounds(container); } return getTaskBoundsFromActivity(activity); } @NonNull static Rect getTaskBoundsFromActivity(@NonNull Activity activity) { final WindowConfiguration windowConfiguration = activity.getResources().getConfiguration().windowConfiguration; if (!activity.isInMultiWindowMode()) { // In fullscreen mode the max bounds should correspond to the task bounds. return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds(); return windowConfiguration.getMaxBounds(); } return activity.getResources().getConfiguration().windowConfiguration.getBounds(); return windowConfiguration.getBounds(); } }
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; import android.window.TaskFragmentInfo; import java.util.ArrayList; import java.util.List; import java.util.Set; /** Represents TaskFragments and split pairs below a Task. */ class TaskContainer { /** The unique task id. */ private final int mTaskId; /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); /** Configuration of the Task. */ @Nullable private Configuration mConfiguration; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /** Active split pairs in this Task. */ final List<SplitContainer> mSplitContainers = new ArrayList<>(); /** * TaskFragments that the organizer has requested to be closed. They should be removed when * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event * for them. */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); TaskContainer(int taskId) { mTaskId = taskId; } int getTaskId() { return mTaskId; } @NonNull Rect getTaskBounds() { return mTaskBounds; } /** Returns {@code true} if the bounds is changed. */ boolean setTaskBounds(@NonNull Rect taskBounds) { if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) { mTaskBounds.set(taskBounds); return true; } return false; } /** Whether the Task bounds has been initialized. */ boolean isTaskBoundsInitialized() { return !mTaskBounds.isEmpty(); } @Nullable Configuration getConfiguration() { return mConfiguration; } void setConfiguration(@Nullable Configuration configuration) { mConfiguration = configuration; } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ boolean isEmpty() { return mContainers.isEmpty() && mFinishedContainer.isEmpty(); } }
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +25 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; Loading @@ -29,12 +32,12 @@ import static org.mockito.Mockito.never; import android.app.Activity; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.window.extensions.embedding.SplitController.TaskContainer; import org.junit.Before; import org.junit.Test; Loading @@ -53,6 +56,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class SplitControllerTest { private static final int TASK_ID = 10; private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); @Mock private Activity mActivity; Loading @@ -70,8 +74,11 @@ public class SplitControllerTest { mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); spyOn(mSplitPresenter); final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); doReturn(mActivityResources).when(mActivity).getResources(); doReturn(new Configuration()).when(mActivityResources).getConfiguration(); doReturn(activityConfig).when(mActivityResources).getConfiguration(); } @Test Loading Loading @@ -117,4 +124,20 @@ public class SplitControllerTest { verify(mSplitController).removeContainer(tf); verify(mActivity, never()).finish(); } @Test public void testNewContainer() { // Must pass in a valid activity. assertThrows(IllegalArgumentException.class, () -> mSplitController.newContainer(null /* activity */, TASK_ID)); assertThrows(IllegalArgumentException.class, () -> mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertNotNull(tf); assertNotNull(taskContainer); assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds()); } }
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.window.extensions.embedding; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; /** * Test class for {@link TaskContainer}. * * Build/Install/Run: * atest WMJetpackUnitTests:TaskContainerTest */ @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class TaskContainerTest { private static final int TASK_ID = 10; private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); @Test public void testIsTaskBoundsInitialized() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); assertFalse(taskContainer.isTaskBoundsInitialized()); taskContainer.setTaskBounds(TASK_BOUNDS); assertTrue(taskContainer.isTaskBoundsInitialized()); } @Test public void testSetTaskBounds() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); assertFalse(taskContainer.setTaskBounds(new Rect())); assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS)); assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS)); } @Test public void testIsEmpty() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); assertTrue(taskContainer.isEmpty()); final TaskFragmentContainer tf = new TaskFragmentContainer(null, TASK_ID); taskContainer.mContainers.add(tf); assertFalse(taskContainer.isEmpty()); taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken()); taskContainer.mContainers.clear(); assertFalse(taskContainer.isEmpty()); } }