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
This commit is contained in:
Chris Li
2022-04-29 13:14:56 +08:00
parent 10e4a19ef4
commit f8a165d8b0
8 changed files with 207 additions and 23 deletions

View File

@@ -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(

View File

@@ -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.

View File

@@ -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());

View File

@@ -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.

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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,

View File

@@ -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();