diff options
Diffstat (limited to 'libs')
5 files changed, 257 insertions, 87 deletions
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 e20cef2bec4e..ca420c64e961 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -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; @@ -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; @@ -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; } @@ -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; @@ -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. @@ -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. @@ -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; } } @@ -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; @@ -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. @@ -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(); - } - } } 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 1b49585ed7dc..716a087203d3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -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; @@ -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(), @@ -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(), @@ -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); @@ -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(); + final int taskId = container.getTaskId(); + final TaskContainer taskContainer = mController.getTaskContainer(taskId); + if (taskContainer == null) { + throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId); } - - // 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"); - } - return bounds; + return taskContainer.getTaskBounds(); } @NonNull @@ -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(); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java new file mode 100644 index 000000000000..be793018d969 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -0,0 +1,97 @@ +/* + * 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(); + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 72519dc6da5f..e0fda58fd664 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -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; @@ -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; @@ -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; @@ -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 @@ -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()); + } } 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 new file mode 100644 index 000000000000..9fb08dffbab8 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -0,0 +1,82 @@ +/* + * 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()); + } +} |