Reset embedded TaskFragment override when entering PIP
When an embedded activity is entering PIP when it is the only activity in the Task, the whole Task will enter PIP. When this happens, we want to reset all organizer override config so that the activity will get the correct config. We don't want to remove the TaskFragment because we can use it to get the windowing mode change in organizer. Besides that, we will disallow any further organizer override to the TaskFragment in PIP. Bug: 225371112 Test: atest WmTests:RootWindowContainerTests Test: atest WmTests:TaskFragmentTest Test: atest WmTests:TaskFragmentOrganizerControllerTest Change-Id: I03bd679b77f7ed8a65ba5fc59723b83a4254442c Merged-In: I03bd679b77f7ed8a65ba5fc59723b83a4254442c
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,6 +190,14 @@ public abstract class ConfigurationContainer<E extends 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<E extends 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<DisplayContent>
|
||||
// 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<DisplayContent>
|
||||
// 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()) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -339,7 +339,7 @@ class TaskFragment extends WindowContainer<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<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;
|
||||
}
|
||||
|
||||
@@ -2761,7 +2761,10 @@ class WindowContainer<E extends 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ActivityRecord> taskActivities = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user