diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index eb912d4c2747b..36a7c7756a90e 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2481,6 +2481,12 @@ class ActivityStarter { if (inTaskFragment == null) { inTaskFragment = TaskFragment.fromTaskFragmentToken( mOptions.getLaunchTaskFragmentToken(), mService); + if (inTaskFragment != null && inTaskFragment.isEmbeddedTaskFragmentInPip()) { + // Do not start activity in TaskFragment in a PIP Task. + Slog.w(TAG, "Can not start activity in TaskFragment in PIP: " + + inTaskFragment); + inTaskFragment = null; + } } } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 08a9da467162b..dbc08cd5d1a94 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -190,6 +190,14 @@ public abstract class ConfigurationContainer { * @see #mFullConfiguration */ public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { + updateRequestedOverrideConfiguration(overrideConfiguration); + // Update full configuration of this container and all its children. + final ConfigurationContainer parent = getParent(); + onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY); + } + + /** Updates override configuration without recalculate full config. */ + void updateRequestedOverrideConfiguration(Configuration overrideConfiguration) { // Pre-compute this here, so we don't need to go through the entire Configuration when // writing to proto (which has significant cost if we write a lot of empty configurations). mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration); @@ -199,9 +207,6 @@ public abstract class ConfigurationContainer { && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) { mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds); } - // Update full configuration of this container and all its children. - final ConfigurationContainer parent = getParent(); - onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY); } /** diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index cc99f377bfeed..d460616496769 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; +import static android.content.res.Configuration.EMPTY; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -2005,7 +2006,8 @@ class RootWindowContainer extends WindowContainer // of the activity entering PIP r.getDisplayContent().prepareAppTransition(TRANSIT_NONE); - final boolean singleActivity = task.getChildCount() == 1; + // TODO: Does it make sense to only count non-finishing activities? + final boolean singleActivity = task.getActivityCount() == 1; final Task rootTask; if (singleActivity) { rootTask = task; @@ -2086,6 +2088,15 @@ class RootWindowContainer extends WindowContainer // TODO(task-org): Figure-out more structured way to do this long term. r.setWindowingMode(intermediateWindowingMode); r.mWaitForEnteringPinnedMode = true; + rootTask.forAllTaskFragments(tf -> { + // When the Task is entering picture-in-picture, we should clear all override from + // the client organizer, so the PIP activity can get the correct config from the + // Task, and prevent conflict with the PipTaskOrganizer. + if (tf.isOrganizedTaskFragment()) { + tf.resetAdjacentTaskFragment(); + tf.updateRequestedOverrideConfiguration(EMPTY); + } + }); rootTask.setWindowingMode(WINDOWING_MODE_PINNED); // Set the launch bounds for launch-into-pip Activity on the root task. if (r.getOptions() != null && r.getOptions().isLaunchIntoPip()) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a46544d6c9021..ac048434c0605 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1380,6 +1380,14 @@ class Task extends TaskFragment { return getActivity(ActivityRecord::canBeTopRunning); } + int getActivityCount() { + final int[] activityCount = new int[1]; + forAllActivities(ar -> { + activityCount[0]++; + }); + return activityCount[0]; + } + /** * Return true if any activities in this task belongs to input uid. */ diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b96b461f2da58..83bd979a8e2f5 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -339,7 +339,7 @@ class TaskFragment extends WindowContainer { } } - private void resetAdjacentTaskFragment() { + void resetAdjacentTaskFragment() { // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { mAdjacentTaskFragment.mAdjacentTaskFragment = null; @@ -2317,6 +2317,14 @@ class TaskFragment extends WindowContainer { mMinHeight = minHeight; } + /** + * Whether this is an embedded TaskFragment in PIP Task. We don't allow any client config + * override for such TaskFragment to prevent flight with PipTaskOrganizer. + */ + boolean isEmbeddedTaskFragmentInPip() { + return isOrganizedTaskFragment() && getTask() != null && getTask().inPinnedWindowingMode(); + } + boolean shouldRemoveSelfOnLastChildRemoval() { return !mCreatedByOrganizer || mIsRemovalRequested; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 5b87463d889e2..4dbcea1e47519 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2761,7 +2761,10 @@ class WindowContainer extends ConfigurationContainer< boolean canStartChangeTransition() { return !mWmService.mDisableTransitionAnimation && mDisplayContent != null && getSurfaceControl() != null && !mDisplayContent.inTransition() - && isVisible() && isVisibleRequested() && okToAnimate(); + && isVisible() && isVisibleRequested() && okToAnimate() + // Pip animation will be handled by PipTaskOrganizer. + && !inPinnedWindowingMode() && getParent() != null + && !getParent().inPinnedWindowingMode(); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index b5cf708eb46d3..c1c8b81f2c324 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -677,15 +677,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { final IBinder fragmentToken = hop.getContainer(); - if (!mLaunchTaskFragments.containsKey(fragmentToken)) { + final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); + if (tf == null) { final Throwable exception = new IllegalArgumentException( "Not allowed to operate with invalid fragment token"); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } + if (tf.isEmbeddedTaskFragmentInPip()) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to start activity in PIP TaskFragment"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } final Intent activityIntent = hop.getActivityIntent(); final Bundle activityOptions = hop.getLaunchOptions(); - final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); final int result = mService.getActivityStartController() .startActivityInTaskFragment(tf, activityIntent, activityOptions, hop.getCallingActivity(), caller.mUid, caller.mPid); @@ -707,6 +713,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } + if (parent.isEmbeddedTaskFragmentInPip()) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to reparent activity to PIP TaskFragment"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } if (!parent.isAllowedToEmbedActivity(activity)) { final Throwable exception = new SecurityException( "The task fragment is not trusted to embed the given activity."); @@ -730,6 +742,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } + if (tf1.isEmbeddedTaskFragmentInPip() + || (tf2 != null && tf2.isEmbeddedTaskFragmentInPip())) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to set adjacent on TaskFragment in PIP Task"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */); effects |= TRANSACT_EFFECTS_LIFECYCLE; @@ -1092,6 +1111,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" + " organizer root1=" + root1 + " root2=" + root2); } + if (root1.isEmbeddedTaskFragmentInPip() || root2.isEmbeddedTaskFragmentInPip()) { + Slog.e(TAG, "Attempt to set adjacent TaskFragment in PIP Task"); + return 0; + } root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether()); return TRANSACT_EFFECTS_LIFECYCLE; } @@ -1105,6 +1128,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private int applyWindowContainerChange(WindowContainer wc, WindowContainerTransaction.Change c) { sanitizeWindowContainer(wc); + if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) { + // No override from organizer for embedded TaskFragment in a PIP Task. + return 0; + } int effects = applyChanges(wc, c); @@ -1420,21 +1447,28 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return; } // The ownerActivity has to belong to the same app as the target Task. - if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() - || ownerActivity.getTask().effectiveUid != caller.mUid) { + final Task ownerTask = ownerActivity.getTask(); + if (ownerTask.effectiveUid != ownerActivity.getUid() + || ownerTask.effectiveUid != caller.mUid) { final Throwable exception = new SecurityException("Not allowed to operate with the ownerToken while " + "the root activity of the target task belong to the different app"); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } + if (ownerTask.inPinnedWindowingMode()) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to create TaskFragment in PIP Task"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } final TaskFragment taskFragment = new TaskFragment(mService, creationParams.getFragmentToken(), true /* createdByOrganizer */); // Set task fragment organizer immediately, since it might have to be notified about further // actions. taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), ownerActivity.getUid(), ownerActivity.info.processName); - ownerActivity.getTask().addChild(taskFragment, POSITION_TOP); + ownerTask.addChild(taskFragment, POSITION_TOP); taskFragment.setWindowingMode(creationParams.getWindowingMode()); taskFragment.setBounds(creationParams.getInitialBounds()); mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); @@ -1467,6 +1501,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return; } } + if (newParentTF.isEmbeddedTaskFragmentInPip() || oldParent.isEmbeddedTaskFragmentInPip()) { + final Throwable exception = new SecurityException( + "Not allow to reparent in TaskFragment in PIP Task."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } while (oldParent.hasChild()) { oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP); } @@ -1482,6 +1522,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return 0; } + if (taskFragment.isEmbeddedTaskFragmentInPip()) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to delete TaskFragment in PIP Task"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return 0; + } mLaunchTaskFragments.removeAt(index); taskFragment.remove(true /* withTransition */, "deleteTaskFragment"); return TRANSACT_EFFECTS_LIFECYCLE; diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 762c08f99d942..8ef9ada8995ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -300,6 +300,81 @@ public class RootWindowContainerTests extends WindowTestsBase { assertTrue(firstActivity.mRequestForceTransition); } + /** + * When there is only one activity in the Task, and the activity is requesting to enter PIP, the + * whole Task should enter PIP. + */ + @Test + public void testSingleActivityTaskEnterPip() { + final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(fullscreenTask) + .build(); + final Task task = activity.getTask(); + + // Move activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(activity, + null /* launchIntoPipHostActivity */, "test"); + + // Ensure a task has moved over. + ensureTaskPlacement(task, activity); + assertTrue(task.inPinnedWindowingMode()); + } + + /** + * When there is only one activity in the Task, and the activity is requesting to enter PIP, the + * whole Task should enter PIP even if the activity is in a TaskFragment. + */ + @Test + public void testSingleActivityInTaskFragmentEnterPip() { + final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(fullscreenTask) + .createActivityCount(1) + .build(); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + final Task task = activity.getTask(); + + // Move activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(activity, + null /* launchIntoPipHostActivity */, "test"); + + // Ensure a task has moved over. + ensureTaskPlacement(task, activity); + assertTrue(task.inPinnedWindowingMode()); + } + + /** + * When there is one TaskFragment with two activities in the Task, the activity requests to + * enter PIP, that activity will be move to PIP root task. + */ + @Test + public void testMultipleActivitiesInTaskFragmentEnterPip() { + final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(fullscreenTask) + .createActivityCount(2) + .build(); + final ActivityRecord firstActivity = taskFragment.getTopMostActivity(); + final ActivityRecord secondActivity = taskFragment.getBottomMostActivity(); + + // Move first activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, + null /* launchIntoPipHostActivity */, "test"); + + final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea(); + final Task pinnedRootTask = taskDisplayArea.getRootPinnedTask(); + + // Ensure a task has moved over. + ensureTaskPlacement(pinnedRootTask, firstActivity); + ensureTaskPlacement(fullscreenTask, secondActivity); + assertTrue(pinnedRootTask.inPinnedWindowingMode()); + assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode()); + } + private static void ensureTaskPlacement(Task task, ActivityRecord... activities) { final ArrayList taskActivities = new ArrayList<>(); 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 37719fecd7e40..d135de0fc9210 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -28,6 +31,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -434,6 +439,107 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } + @Test + public void testTaskFragmentInPip_startActivityInTaskFragment() { + setupTaskFragmentInPip(); + final ActivityRecord activity = mTaskFragment.getTopMostActivity(); + final IBinder errorToken = new Binder(); + spyOn(mAtm.getActivityStartController()); + spyOn(mAtm.mWindowOrganizerController); + + // Not allow to start activity in a TaskFragment that is in a PIP Task. + mTransaction.startActivityInTaskFragment( + mFragmentToken, activity.token, new Intent(), null /* activityOptions */) + .setErrorCallbackToken(errorToken); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(), + any(), any(), anyInt(), anyInt()); + verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), + eq(errorToken), any(IllegalArgumentException.class)); + } + + @Test + public void testTaskFragmentInPip_reparentActivityToTaskFragment() { + setupTaskFragmentInPip(); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final IBinder errorToken = new Binder(); + spyOn(mAtm.mWindowOrganizerController); + + // Not allow to reparent activity to a TaskFragment that is in a PIP Task. + mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token) + .setErrorCallbackToken(errorToken); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), + eq(errorToken), any(IllegalArgumentException.class)); + assertNull(activity.getOrganizedTaskFragment()); + } + + @Test + public void testTaskFragmentInPip_setAdjacentTaskFragment() { + setupTaskFragmentInPip(); + final IBinder errorToken = new Binder(); + spyOn(mAtm.mWindowOrganizerController); + + // Not allow to set adjacent on a TaskFragment that is in a PIP Task. + mTransaction.setAdjacentTaskFragments(mFragmentToken, null /* fragmentToken2 */, + null /* options */) + .setErrorCallbackToken(errorToken); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), + eq(errorToken), any(IllegalArgumentException.class)); + verify(mTaskFragment, never()).setAdjacentTaskFragment(any(), anyBoolean()); + } + + @Test + public void testTaskFragmentInPip_createTaskFragment() { + mController.registerOrganizer(mIOrganizer); + final Task pipTask = createTask(mDisplayContent, WINDOWING_MODE_PINNED, + ACTIVITY_TYPE_STANDARD); + final ActivityRecord activity = createActivityRecord(pipTask); + final IBinder fragmentToken = new Binder(); + final IBinder errorToken = new Binder(); + spyOn(mAtm.mWindowOrganizerController); + + // Not allow to create TaskFragment in a PIP Task. + createTaskFragmentFromOrganizer(mTransaction, activity, fragmentToken); + mTransaction.setErrorCallbackToken(errorToken); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), + eq(errorToken), any(IllegalArgumentException.class)); + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + } + + @Test + public void testTaskFragmentInPip_deleteTaskFragment() { + setupTaskFragmentInPip(); + final IBinder errorToken = new Binder(); + spyOn(mAtm.mWindowOrganizerController); + + // Not allow to delete a TaskFragment that is in a PIP Task. + mTransaction.deleteTaskFragment(mFragmentWindowToken) + .setErrorCallbackToken(errorToken); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), + eq(errorToken), any(IllegalArgumentException.class)); + assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(mFragmentToken)); + } + + @Test + public void testTaskFragmentInPip_setConfig() { + setupTaskFragmentInPip(); + spyOn(mAtm.mWindowOrganizerController); + + // Set bounds is ignored on a TaskFragment that is in a PIP Task. + mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100)); + + verify(mTaskFragment, never()).setBounds(any()); + } + @Test public void testDeferPendingTaskFragmentEventsOfInvisibleTask() { // Task - TaskFragment - Activity. @@ -643,4 +749,20 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { fail(); } } + + /** Setups an embedded TaskFragment in a PIP Task. */ + private void setupTaskFragmentInPip() { + mOrganizer.applyTransaction(mTransaction); + mController.registerOrganizer(mIOrganizer); + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setCreateParentTask() + .setFragmentToken(mFragmentToken) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken(); + mAtm.mWindowOrganizerController.mLaunchTaskFragments + .put(mFragmentToken, mTaskFragment); + mTaskFragment.getTask().setWindowingMode(WINDOWING_MODE_PINNED); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 2101b6e767c89..30c2413eb6f74 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -31,6 +32,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.clearInvocations; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; import android.platform.test.annotations.Presubmit; @@ -206,6 +208,39 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(secondaryActivity.supportsEnterPipOnTaskSwitch); } + @Test + public void testEmbeddedTaskFragmentEnterPip_resetOrganizerOverrideConfig() { + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .setCreateParentTask() + .createActivityCount(1) + .build(); + final Task task = taskFragment.getTask(); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + final Rect taskFragmentBounds = new Rect(0, 0, 300, 1000); + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + taskFragment.setBounds(taskFragmentBounds); + + assertEquals(taskFragmentBounds, activity.getBounds()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); + + // Move activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(activity, + null /* launchIntoPipHostActivity */, "test"); + + // Ensure taskFragment requested config is reset. + assertEquals(taskFragment, activity.getOrganizedTaskFragment()); + assertEquals(task, activity.getTask()); + assertTrue(task.inPinnedWindowingMode()); + assertTrue(taskFragment.inPinnedWindowingMode()); + final Rect taskBounds = task.getBounds(); + assertEquals(taskBounds, taskFragment.getBounds()); + assertEquals(taskBounds, activity.getBounds()); + assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration()); + } + @Test public void testActivityHasOverlayOverUntrustedModeEmbedded() { final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,