Merge changes from topic "presubmit-am-dec5ac638a7e44c68413c7f552c67cd0" into tm-mainline-prod

* changes:
  [automerge] Reset embedded TaskFragment override when entering PIP 2p: 103a6dc7f7
  Reset embedded TaskFragment override when entering PIP
This commit is contained in:
Chris Li
2022-04-13 13:09:46 +00:00
committed by Android (Google) Code Review
10 changed files with 330 additions and 11 deletions

View File

@@ -2481,6 +2481,12 @@ class ActivityStarter {
if (inTaskFragment == null) { if (inTaskFragment == null) {
inTaskFragment = TaskFragment.fromTaskFragmentToken( inTaskFragment = TaskFragment.fromTaskFragmentToken(
mOptions.getLaunchTaskFragmentToken(), mService); 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;
}
} }
} }

View File

@@ -190,6 +190,14 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
* @see #mFullConfiguration * @see #mFullConfiguration
*/ */
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { 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 // 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). // writing to proto (which has significant cost if we write a lot of empty configurations).
mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration); mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
@@ -199,9 +207,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
&& diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) { && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) {
mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds); 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);
} }
/** /**

View File

@@ -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.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; 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.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.INVALID_DISPLAY;
@@ -2005,7 +2006,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// of the activity entering PIP // of the activity entering PIP
r.getDisplayContent().prepareAppTransition(TRANSIT_NONE); 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; final Task rootTask;
if (singleActivity) { if (singleActivity) {
rootTask = task; rootTask = task;
@@ -2086,6 +2088,15 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// TODO(task-org): Figure-out more structured way to do this long term. // TODO(task-org): Figure-out more structured way to do this long term.
r.setWindowingMode(intermediateWindowingMode); r.setWindowingMode(intermediateWindowingMode);
r.mWaitForEnteringPinnedMode = true; 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); rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
// Set the launch bounds for launch-into-pip Activity on the root task. // Set the launch bounds for launch-into-pip Activity on the root task.
if (r.getOptions() != null && r.getOptions().isLaunchIntoPip()) { if (r.getOptions() != null && r.getOptions().isLaunchIntoPip()) {

View File

@@ -1380,6 +1380,14 @@ class Task extends TaskFragment {
return getActivity(ActivityRecord::canBeTopRunning); 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. * Return true if any activities in this task belongs to input uid.
*/ */

View File

@@ -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. // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
mAdjacentTaskFragment.mAdjacentTaskFragment = null; mAdjacentTaskFragment.mAdjacentTaskFragment = null;
@@ -2317,6 +2317,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mMinHeight = minHeight; 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() { boolean shouldRemoveSelfOnLastChildRemoval() {
return !mCreatedByOrganizer || mIsRemovalRequested; return !mCreatedByOrganizer || mIsRemovalRequested;
} }

View File

@@ -2761,7 +2761,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
boolean canStartChangeTransition() { boolean canStartChangeTransition() {
return !mWmService.mDisableTransitionAnimation && mDisplayContent != null return !mWmService.mDisableTransitionAnimation && mDisplayContent != null
&& getSurfaceControl() != null && !mDisplayContent.inTransition() && getSurfaceControl() != null && !mDisplayContent.inTransition()
&& isVisible() && isVisibleRequested() && okToAnimate(); && isVisible() && isVisibleRequested() && okToAnimate()
// Pip animation will be handled by PipTaskOrganizer.
&& !inPinnedWindowingMode() && getParent() != null
&& !getParent().inPinnedWindowingMode();
} }
/** /**

View File

@@ -677,15 +677,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
} }
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
final IBinder fragmentToken = hop.getContainer(); final IBinder fragmentToken = hop.getContainer();
if (!mLaunchTaskFragments.containsKey(fragmentToken)) { final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
if (tf == null) {
final Throwable exception = new IllegalArgumentException( final Throwable exception = new IllegalArgumentException(
"Not allowed to operate with invalid fragment token"); "Not allowed to operate with invalid fragment token");
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
break; 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 Intent activityIntent = hop.getActivityIntent();
final Bundle activityOptions = hop.getLaunchOptions(); final Bundle activityOptions = hop.getLaunchOptions();
final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
final int result = mService.getActivityStartController() final int result = mService.getActivityStartController()
.startActivityInTaskFragment(tf, activityIntent, activityOptions, .startActivityInTaskFragment(tf, activityIntent, activityOptions,
hop.getCallingActivity(), caller.mUid, caller.mPid); hop.getCallingActivity(), caller.mUid, caller.mPid);
@@ -707,6 +713,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
break; 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)) { if (!parent.isAllowedToEmbedActivity(activity)) {
final Throwable exception = new SecurityException( final Throwable exception = new SecurityException(
"The task fragment is not trusted to embed the given activity."); "The task fragment is not trusted to embed the given activity.");
@@ -730,6 +742,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
break; 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 */); tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */);
effects |= TRANSACT_EFFECTS_LIFECYCLE; effects |= TRANSACT_EFFECTS_LIFECYCLE;
@@ -1092,6 +1111,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+ " organizer root1=" + root1 + " root2=" + root2); + " 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()); root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether());
return TRANSACT_EFFECTS_LIFECYCLE; return TRANSACT_EFFECTS_LIFECYCLE;
} }
@@ -1105,6 +1128,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
private int applyWindowContainerChange(WindowContainer wc, private int applyWindowContainerChange(WindowContainer wc,
WindowContainerTransaction.Change c) { WindowContainerTransaction.Change c) {
sanitizeWindowContainer(wc); 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); int effects = applyChanges(wc, c);
@@ -1420,21 +1447,28 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return; return;
} }
// The ownerActivity has to belong to the same app as the target Task. // The ownerActivity has to belong to the same app as the target Task.
if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() final Task ownerTask = ownerActivity.getTask();
|| ownerActivity.getTask().effectiveUid != caller.mUid) { if (ownerTask.effectiveUid != ownerActivity.getUid()
|| ownerTask.effectiveUid != caller.mUid) {
final Throwable exception = final Throwable exception =
new SecurityException("Not allowed to operate with the ownerToken while " new SecurityException("Not allowed to operate with the ownerToken while "
+ "the root activity of the target task belong to the different app"); + "the root activity of the target task belong to the different app");
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
return; 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, final TaskFragment taskFragment = new TaskFragment(mService,
creationParams.getFragmentToken(), true /* createdByOrganizer */); creationParams.getFragmentToken(), true /* createdByOrganizer */);
// Set task fragment organizer immediately, since it might have to be notified about further // Set task fragment organizer immediately, since it might have to be notified about further
// actions. // actions.
taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
ownerActivity.getUid(), ownerActivity.info.processName); ownerActivity.getUid(), ownerActivity.info.processName);
ownerActivity.getTask().addChild(taskFragment, POSITION_TOP); ownerTask.addChild(taskFragment, POSITION_TOP);
taskFragment.setWindowingMode(creationParams.getWindowingMode()); taskFragment.setWindowingMode(creationParams.getWindowingMode());
taskFragment.setBounds(creationParams.getInitialBounds()); taskFragment.setBounds(creationParams.getInitialBounds());
mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
@@ -1467,6 +1501,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return; 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()) { while (oldParent.hasChild()) {
oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP); oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP);
} }
@@ -1482,6 +1522,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
return 0; 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); mLaunchTaskFragments.removeAt(index);
taskFragment.remove(true /* withTransition */, "deleteTaskFragment"); taskFragment.remove(true /* withTransition */, "deleteTaskFragment");
return TRANSACT_EFFECTS_LIFECYCLE; return TRANSACT_EFFECTS_LIFECYCLE;

View File

@@ -300,6 +300,81 @@ public class RootWindowContainerTests extends WindowTestsBase {
assertTrue(firstActivity.mRequestForceTransition); 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) { private static void ensureTaskPlacement(Task task, ActivityRecord... activities) {
final ArrayList<ActivityRecord> taskActivities = new ArrayList<>(); final ArrayList<ActivityRecord> taskActivities = new ArrayList<>();

View File

@@ -16,6 +16,9 @@
package com.android.server.wm; 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.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP; 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.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; 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.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -434,6 +439,107 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); 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 @Test
public void testDeferPendingTaskFragmentEventsOfInvisibleTask() { public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
// Task - TaskFragment - Activity. // Task - TaskFragment - Activity.
@@ -643,4 +749,20 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
fail(); 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);
}
} }

View File

@@ -17,6 +17,7 @@
package com.android.server.wm; package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 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 android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; 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.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Binder; import android.os.Binder;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -206,6 +208,39 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(secondaryActivity.supportsEnterPipOnTaskSwitch); 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 @Test
public void testActivityHasOverlayOverUntrustedModeEmbedded() { public void testActivityHasOverlayOverUntrustedModeEmbedded() {
final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,