From f8a165d8b0aa9ab4b9b9b6f1c92e93d0158e814b Mon Sep 17 00:00:00 2001 From: Chris Li Date: Fri, 29 Apr 2022 13:14:56 +0800 Subject: [PATCH] Notify organizer about TaskFragment parent windowing mode change 1. When the Task windowing mode changes, notify the organizer. 2. When organizer requests windowing mode change, record the last request value to avoid unnecessary IPC call. Bug: 230446204 Test: atest WmTests:TaskFragmentOrganizerControllerTest Test: atest WMJetpackUnitTests:SplitPresenterTest Change-Id: I30c5dbf7b70d085647490943508bd5d22fda3ead --- .../JetpackTaskFragmentOrganizer.java | 7 +- .../extensions/embedding/SplitController.java | 2 + .../extensions/embedding/SplitPresenter.java | 51 ++++--- .../extensions/embedding/TaskContainer.java | 2 +- .../embedding/TaskFragmentContainer.java | 22 +++ .../embedding/SplitPresenterTest.java | 132 ++++++++++++++++++ .../wm/TaskFragmentOrganizerController.java | 4 +- .../TaskFragmentOrganizerControllerTest.java | 10 +- 8 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java 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 81caf7786cf52..3ff531573f1ff 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -133,8 +133,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { // Create or resize the launching TaskFragment. if (mFragmentInfos.containsKey(launchingFragmentToken)) { resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds); - wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(), - windowingMode); + updateWindowingMode(wct, launchingFragmentToken, windowingMode); } else { createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, launchingFragmentBounds, windowingMode, launchingActivity); @@ -157,7 +156,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); + updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); } /** @@ -260,7 +259,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds); } - private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken, + void updateWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken, @WindowingMode int windowingMode) { 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 b370e59ac7c83..54d9c55f75c39 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -17,6 +17,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -179,6 +180,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Enter PIP. // All overrides will be cleanup. container.setLastRequestedBounds(null /* bounds */); + container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED); cleanupForEnterPip(wct, container); } else if (wasInPip) { // Exit PIP. 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 d8423499730f2..06c1d4ec8d326 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -30,7 +30,6 @@ import android.util.LayoutDirection; import android.view.View; import android.view.WindowInsets; import android.view.WindowMetrics; -import android.window.TaskFragmentCreationParams; import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; @@ -122,7 +121,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, windowingMode); - secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); @@ -198,18 +196,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container = mController.newContainer(activity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(bounds); - final TaskFragmentCreationParams fragmentOptions = - createFragmentOptions( - container.getTaskFragmentToken(), - activity.getActivityToken(), - bounds, - windowingMode); - wct.createTaskFragment(fragmentOptions); - + createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(), + bounds, windowingMode); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), activity.getActivityToken()); - - container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); final int windowingMode = mController.getTaskContainer(taskId) @@ -262,9 +252,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } applyTransaction(wct); - - primaryContainer.setLastRequestedBounds(primaryRectBounds); - secondaryContainer.setLastRequestedBounds(secondaryRectBounds); } /** @@ -346,10 +333,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull TaskFragmentContainer container, @WindowingMode int windowingMode) { if (container.getInfo() != null) { - wct.setWindowingMode(container.getInfo().getToken(), windowingMode); + updateWindowingMode(wct, container.getTaskFragmentToken(), windowingMode); } } + @Override + void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, + @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) { + final TaskFragmentContainer container = mController.getContainer(fragmentToken); + if (container == null) { + throw new IllegalStateException( + "Creating a task fragment that is not registered with controller."); + } + + container.setLastRequestedBounds(bounds); + container.setLastRequestedWindowingMode(windowingMode); + super.createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode); + } + @Override void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds) { @@ -368,6 +369,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.resizeTaskFragment(wct, fragmentToken, bounds); } + @Override + void updateWindowingMode(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) { + final TaskFragmentContainer container = mController.getContainer(fragmentToken); + if (container == null) { + throw new IllegalStateException("Setting windowing mode for a task fragment that is" + + " not registered with controller."); + } + + if (container.isLastRequestedWindowingModeEqual(windowingMode)) { + // Return early if the windowing mode were already requested + return; + } + + container.setLastRequestedWindowingMode(windowingMode); + super.updateWindowingMode(wct, fragmentToken, windowingMode); + } + boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer()); return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule()); 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 3c0762d81494e..a70755560eb16 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -106,7 +106,7 @@ class TaskContainer { 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()) { + if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) { return WINDOWING_MODE_UNDEFINED; } // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 871f545d203a1..fe422bf37a698 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -17,11 +17,13 @@ package androidx.window.extensions.embedding; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityThread; +import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -72,6 +74,12 @@ class TaskFragmentContainer { */ private final Rect mLastRequestedBounds = new Rect(); + /** + * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}. + */ + @WindowingMode + private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED; + /** * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. @@ -300,6 +308,20 @@ class TaskFragmentContainer { } } + /** + * Checks if last requested windowing mode is equal to the provided value. + */ + boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) { + return mLastRequestedWindowingMode == windowingMode; + } + + /** + * Updates the last requested windowing mode. + */ + void setLastRequestedWindowingMode(@WindowingMode int windowingModes) { + mLastRequestedWindowingMode = windowingModes; + } + /** Gets the parent leaf Task id. */ int getTaskId() { return mTaskId; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java new file mode 100644 index 0000000000000..906e9904566fa --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -0,0 +1,132 @@ +/* + * 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 android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +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 android.window.WindowContainerTransaction; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link SplitPresenter}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:SplitPresenterTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SplitPresenterTest { + private static final int TASK_ID = 10; + private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); + + @Mock + private Activity mActivity; + @Mock + private Resources mActivityResources; + @Mock + private TaskFragmentInfo mTaskFragmentInfo; + @Mock + private WindowContainerTransaction mTransaction; + private SplitController mController; + private SplitPresenter mPresenter; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new SplitController(); + mPresenter = mController.mPresenter; + spyOn(mController); + spyOn(mPresenter); + final Configuration activityConfig = new Configuration(); + activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); + activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); + doReturn(mActivityResources).when(mActivity).getResources(); + doReturn(activityConfig).when(mActivityResources).getConfiguration(); + } + + @Test + public void testCreateTaskFragment() { + final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + mPresenter.createTaskFragment(mTransaction, container.getTaskFragmentToken(), + mActivity.getActivityToken(), TASK_BOUNDS, WINDOWING_MODE_MULTI_WINDOW); + + assertTrue(container.areLastRequestedBoundsEqual(TASK_BOUNDS)); + assertTrue(container.isLastRequestedWindowingModeEqual(WINDOWING_MODE_MULTI_WINDOW)); + verify(mTransaction).createTaskFragment(any()); + } + + @Test + public void testResizeTaskFragment() { + final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + mPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), mTaskFragmentInfo); + mPresenter.resizeTaskFragment(mTransaction, container.getTaskFragmentToken(), TASK_BOUNDS); + + assertTrue(container.areLastRequestedBoundsEqual(TASK_BOUNDS)); + verify(mTransaction).setBounds(any(), eq(TASK_BOUNDS)); + + // No request to set the same bounds. + clearInvocations(mTransaction); + mPresenter.resizeTaskFragment(mTransaction, container.getTaskFragmentToken(), TASK_BOUNDS); + + verify(mTransaction, never()).setBounds(any(), any()); + } + + @Test + public void testUpdateWindowingMode() { + final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + mPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), mTaskFragmentInfo); + mPresenter.updateWindowingMode(mTransaction, container.getTaskFragmentToken(), + WINDOWING_MODE_MULTI_WINDOW); + + assertTrue(container.isLastRequestedWindowingModeEqual(WINDOWING_MODE_MULTI_WINDOW)); + verify(mTransaction).setWindowingMode(any(), eq(WINDOWING_MODE_MULTI_WINDOW)); + + // No request to set the same windowing mode. + clearInvocations(mTransaction); + mPresenter.updateWindowingMode(mTransaction, container.getTaskFragmentToken(), + WINDOWING_MODE_MULTI_WINDOW); + + verify(mTransaction, never()).setWindowingMode(any(), anyInt()); + + } +} diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index eaf25260405a6..6a7873998723c 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -197,7 +197,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr final Task parent = tf.getParent().asTask(); final Configuration parentConfig = parent.getConfiguration(); final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf); - if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)) { + if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig) + && parentConfig.windowConfiguration.getWindowingMode() + == lastParentConfig.windowConfiguration.getWindowingMode()) { return; } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 7a704742fba2f..24d04da49e54a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -202,9 +202,17 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), any()); - // Trigger callback if the info is changed. + // Trigger callback if the size is changed. parentConfig.smallestScreenWidthDp = 100; + mController.onTaskFragmentParentInfoChanged( + mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any()); + + // Trigger callback if the windowing mode is changed. + clearInvocations(mOrganizer); + parentConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); mController.onTaskFragmentParentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents();