Merge "Allow TaskFragmentOrganizer to apply WCT without permission" into sc-v2-dev

This commit is contained in:
Chris Li
2021-07-16 06:50:53 +00:00
committed by Android (Google) Code Review
10 changed files with 357 additions and 17 deletions

View File

@@ -3256,8 +3256,8 @@ package android.window {
public class WindowOrganizer { public class WindowOrganizer {
ctor public WindowOrganizer(); ctor public WindowOrganizer();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction); method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
} }
@UiContext public abstract class WindowProviderService extends android.app.Service { @UiContext public abstract class WindowProviderService extends android.app.Service {

View File

@@ -265,6 +265,7 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
} }
}; };
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
private IDisplayAreaOrganizerController getController() { private IDisplayAreaOrganizerController getController() {
try { try {
return getWindowOrganizerController().getDisplayAreaOrganizerController(); return getWindowOrganizerController().getDisplayAreaOrganizerController();
@@ -272,5 +273,4 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
return null; return null;
} }
} }
} }

View File

@@ -120,6 +120,19 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
public void onTaskFragmentError( public void onTaskFragmentError(
@NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {} @NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
@Override
public void applyTransaction(@NonNull WindowContainerTransaction t) {
t.setTaskFragmentOrganizer(mInterface);
super.applyTransaction(t);
}
@Override
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
t.setTaskFragmentOrganizer(mInterface);
return super.applySyncTransaction(t, callback);
}
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
@Override @Override
public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) { public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) {

View File

@@ -290,6 +290,7 @@ public class TaskOrganizer extends WindowOrganizer {
} }
}; };
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
private ITaskOrganizerController getController() { private ITaskOrganizerController getController() {
try { try {
return getWindowOrganizerController().getTaskOrganizerController(); return getWindowOrganizerController().getTaskOrganizerController();

View File

@@ -35,6 +35,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
/** /**
* Represents a collection of operations on some WindowContainers that should be applied all at * Represents a collection of operations on some WindowContainers that should be applied all at
@@ -52,12 +53,16 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable @Nullable
private IBinder mErrorCallbackToken; private IBinder mErrorCallbackToken;
@Nullable
private ITaskFragmentOrganizer mTaskFragmentOrganizer;
public WindowContainerTransaction() {} public WindowContainerTransaction() {}
private WindowContainerTransaction(Parcel in) { private WindowContainerTransaction(Parcel in) {
in.readMap(mChanges, null /* loader */); in.readMap(mChanges, null /* loader */);
in.readList(mHierarchyOps, null /* loader */); in.readList(mHierarchyOps, null /* loader */);
mErrorCallbackToken = in.readStrongBinder(); mErrorCallbackToken = in.readStrongBinder();
mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
} }
private Change getOrCreateChange(IBinder token) { private Change getOrCreateChange(IBinder token) {
@@ -473,7 +478,7 @@ public final class WindowContainerTransaction implements Parcelable {
final HierarchyOp hierarchyOp = final HierarchyOp hierarchyOp =
new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN) new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN)
.setContainer(oldParent.asBinder()) .setContainer(oldParent.asBinder())
.setReparentContainer(newParent.asBinder()) .setReparentContainer(newParent != null ? newParent.asBinder() : null)
.build(); .build();
mHierarchyOps.add(hierarchyOp); mHierarchyOps.add(hierarchyOp);
return this; return this;
@@ -496,6 +501,23 @@ public final class WindowContainerTransaction implements Parcelable {
return this; return this;
} }
/**
* Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}.
* When this is set, the server side will not check for the permission of
* {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only
* contains operations that are allowed for this organizer, such as modifying TaskFragments that
* are organized by this organizer.
* @hide
*/
@NonNull
WindowContainerTransaction setTaskFragmentOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
if (mTaskFragmentOrganizer != null) {
throw new IllegalStateException("Can't set multiple organizers for one transaction.");
}
mTaskFragmentOrganizer = organizer;
return this;
}
/** /**
* Merges another WCT into this one. * Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving * @param transfer When true, this will transfer everything from other potentially leaving
@@ -519,7 +541,17 @@ public final class WindowContainerTransaction implements Parcelable {
} }
if (mErrorCallbackToken != null && other.mErrorCallbackToken != null && mErrorCallbackToken if (mErrorCallbackToken != null && other.mErrorCallbackToken != null && mErrorCallbackToken
!= other.mErrorCallbackToken) { != other.mErrorCallbackToken) {
throw new IllegalArgumentException("Can't merge two WCT with different error token"); throw new IllegalArgumentException("Can't merge two WCTs with different error token");
}
final IBinder taskFragmentOrganizerAsBinder = mTaskFragmentOrganizer != null
? mTaskFragmentOrganizer.asBinder()
: null;
final IBinder otherTaskFragmentOrganizerAsBinder = other.mTaskFragmentOrganizer != null
? other.mTaskFragmentOrganizer.asBinder()
: null;
if (!Objects.equals(taskFragmentOrganizerAsBinder, otherTaskFragmentOrganizerAsBinder)) {
throw new IllegalArgumentException(
"Can't merge two WCTs from different TaskFragmentOrganizers");
} }
mErrorCallbackToken = mErrorCallbackToken != null mErrorCallbackToken = mErrorCallbackToken != null
? mErrorCallbackToken ? mErrorCallbackToken
@@ -547,11 +579,21 @@ public final class WindowContainerTransaction implements Parcelable {
return mErrorCallbackToken; return mErrorCallbackToken;
} }
/** @hide */
@Nullable
public ITaskFragmentOrganizer getTaskFragmentOrganizer() {
return mTaskFragmentOrganizer;
}
@Override @Override
@NonNull @NonNull
public String toString() { public String toString() {
return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps return "WindowContainerTransaction {"
+ " errorCallbackToken=" + mErrorCallbackToken + " }"; + " changes = " + mChanges
+ " hops = " + mHierarchyOps
+ " errorCallbackToken=" + mErrorCallbackToken
+ " taskFragmentOrganizer=" + mTaskFragmentOrganizer
+ " }";
} }
@Override @Override
@@ -560,6 +602,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeMap(mChanges); dest.writeMap(mChanges);
dest.writeList(mHierarchyOps); dest.writeList(mHierarchyOps);
dest.writeStrongBinder(mErrorCallbackToken); dest.writeStrongBinder(mErrorCallbackToken);
dest.writeStrongInterface(mTaskFragmentOrganizer);
} }
@Override @Override

View File

@@ -36,9 +36,16 @@ public class WindowOrganizer {
/** /**
* Apply multiple WindowContainer operations at once. * Apply multiple WindowContainer operations at once.
*
* Note that using this API requires the caller to hold
* {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using
* {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is
* created by itself.
*
* @param t The transaction to apply. * @param t The transaction to apply.
*/ */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
conditional = true)
public void applyTransaction(@NonNull WindowContainerTransaction t) { public void applyTransaction(@NonNull WindowContainerTransaction t) {
try { try {
if (!t.isEmpty()) { if (!t.isEmpty()) {
@@ -51,6 +58,12 @@ public class WindowOrganizer {
/** /**
* Apply multiple WindowContainer operations at once. * Apply multiple WindowContainer operations at once.
*
* Note that using this API requires the caller to hold
* {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using
* {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is
* created by itself.
*
* @param t The transaction to apply. * @param t The transaction to apply.
* @param callback This transaction will use the synchronization scheme described in * @param callback This transaction will use the synchronization scheme described in
* BLASTSyncEngine.java. The SurfaceControl transaction containing the effects of this * BLASTSyncEngine.java. The SurfaceControl transaction containing the effects of this
@@ -58,7 +71,8 @@ public class WindowOrganizer {
* @return An ID for the sync operation which will later be passed to transactionReady callback. * @return An ID for the sync operation which will later be passed to transactionReady callback.
* This lets the caller differentiate overlapping sync operations. * This lets the caller differentiate overlapping sync operations.
*/ */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
conditional = true)
public int applySyncTransaction(@NonNull WindowContainerTransaction t, public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) { @NonNull WindowContainerTransactionCallback callback) {
try { try {
@@ -123,7 +137,6 @@ public class WindowOrganizer {
} }
} }
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
IWindowOrganizerController getWindowOrganizerController() { IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get(); return IWindowOrganizerControllerSingleton.get();
} }

View File

@@ -3445,7 +3445,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override @Override
public IWindowOrganizerController getWindowOrganizerController() { public IWindowOrganizerController getWindowOrganizerController() {
enforceTaskPermission("getWindowOrganizerController()");
return mWindowOrganizerController; return mWindowOrganizerController;
} }

View File

@@ -289,6 +289,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mTaskFragmentOrganizerPid = pid; mTaskFragmentOrganizerPid = pid;
} }
/** Whether this TaskFragment is organized by the given {@code organizer}. */
boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
return organizer != null && mTaskFragmentOrganizer != null
&& organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
}
TaskFragment getAdjacentTaskFragment() { TaskFragment getAdjacentTaskFragment() {
return mAdjacentTaskFragment; return mAdjacentTaskFragment;
} }

View File

@@ -54,6 +54,7 @@ import android.util.ArraySet;
import android.util.Slog; import android.util.Slog;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizerController; import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController; import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController; import android.window.ITaskOrganizerController;
import android.window.ITransitionPlayer; import android.window.ITransitionPlayer;
@@ -110,7 +111,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final TransitionController mTransitionController; final TransitionController mTransitionController;
/** /**
* A Map which manages the relationship between * A Map which manages the relationship between
* {@link TaskFragmentCreationParams.mFragmentToken fragmentToken} and {@link TaskFragment} * {@link TaskFragmentCreationParams#getFragmentToken()} and {@link TaskFragment}
*/ */
private final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>(); private final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
@@ -139,10 +140,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override @Override
public void applyTransaction(WindowContainerTransaction t) { public void applyTransaction(WindowContainerTransaction t) {
enforceTaskPermission("applyTransaction()");
if (t == null) { if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); throw new IllegalArgumentException("Null transaction passed to applyTransaction");
} }
enforceTaskPermission("applyTransaction()", t);
final CallerInfo caller = new CallerInfo(); final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
@@ -157,10 +158,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override @Override
public int applySyncTransaction(WindowContainerTransaction t, public int applySyncTransaction(WindowContainerTransaction t,
IWindowContainerTransactionCallback callback) { IWindowContainerTransactionCallback callback) {
enforceTaskPermission("applySyncTransaction()");
if (t == null) { if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
} }
enforceTaskPermission("applySyncTransaction()", t);
final CallerInfo caller = new CallerInfo(); final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
@@ -620,7 +621,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break; break;
case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer()); final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
final WindowContainer newParent = WindowContainer.fromBinder(hop.getNewParent()); final WindowContainer newParent = hop.getNewParent() != null
? WindowContainer.fromBinder(hop.getNewParent())
: null;
if (oldParent == null || !oldParent.isAttached()) { if (oldParent == null || !oldParent.isAttached()) {
Slog.e(TAG, "Attempt to operate on unknown or detached container: " Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ oldParent); + oldParent);
@@ -906,6 +909,102 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.enforceTaskPermission(func); mService.enforceTaskPermission(func);
} }
private void enforceTaskPermission(String func, WindowContainerTransaction t) {
if (t == null || t.getTaskFragmentOrganizer() == null) {
enforceTaskPermission(func);
return;
}
// Apps may not have the permission to manage Tasks, but we are allowing apps to manage
// TaskFragments belonging to their own Task.
enforceOperationsAllowedForTaskFragmentOrganizer(func, t);
}
/**
* Makes sure that the transaction only contains operations that are allowed for the
* {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
*/
private void enforceOperationsAllowedForTaskFragmentOrganizer(
String func, WindowContainerTransaction t) {
final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
// Configuration changes
final Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
// Only allow to apply changes to TaskFragment that is created by this organizer.
enforceTaskFragmentOrganized(func, WindowContainer.fromBinder(entry.getKey()),
organizer);
}
// Hierarchy changes
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
for (int i = hops.size() - 1; i >= 0; i--) {
final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
final int type = hop.getType();
// Check for each type of the operations that are allowed for TaskFragmentOrganizer.
switch (type) {
case HIERARCHY_OP_TYPE_REORDER:
case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
enforceTaskFragmentOrganized(func,
WindowContainer.fromBinder(hop.getContainer()), organizer);
break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
enforceTaskFragmentOrganized(func,
WindowContainer.fromBinder(hop.getContainer()), organizer);
enforceTaskFragmentOrganized(func,
WindowContainer.fromBinder(hop.getAdjacentRoot()),
organizer);
break;
case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
// We are allowing organizer to create TaskFragment. We will check the
// ownerToken in #createTaskFragment, and trigger error callback if that is not
// valid.
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
// We are allowing organizer to start/reparent activity to a TaskFragment it
// created. Nothing to check here because the TaskFragment may not be created
// yet, but will be created in the same transaction.
break;
case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
enforceTaskFragmentOrganized(func,
WindowContainer.fromBinder(hop.getContainer()), organizer);
if (hop.getNewParent() != null) {
enforceTaskFragmentOrganized(func,
WindowContainer.fromBinder(hop.getNewParent()),
organizer);
}
break;
default:
// Other types of hierarchy changes are not allowed.
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ " trying to apply a hierarchy change that is not allowed for"
+ " TaskFragmentOrganizer=" + organizer;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
}
private void enforceTaskFragmentOrganized(String func, @Nullable WindowContainer wc,
ITaskFragmentOrganizer organizer) {
if (wc == null) {
Slog.e(TAG, "Attempt to operate on window that no longer exists");
return;
}
final TaskFragment tf = wc.asTaskFragment();
if (tf == null || !tf.hasTaskFragmentOrganizer(organizer)) {
String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid() + " trying to modify window container not"
+ " belonging to the TaskFragmentOrganizer=" + organizer;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams) { void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams) {
final ActivityRecord ownerActivity = final ActivityRecord ownerActivity =
ActivityRecord.forTokenLocked(creationParams.getOwnerToken()); ActivityRecord.forTokenLocked(creationParams.getOwnerToken());

View File

@@ -22,6 +22,8 @@ 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.testing.Assert.assertThrows; import static com.android.server.wm.testing.Assert.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
@@ -29,7 +31,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -37,8 +41,12 @@ import android.os.RemoteException;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -61,18 +69,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
private TaskFragment mTaskFragment; private TaskFragment mTaskFragment;
private TaskFragmentInfo mTaskFragmentInfo; private TaskFragmentInfo mTaskFragmentInfo;
private IBinder mFragmentToken; private IBinder mFragmentToken;
private WindowContainerTransaction mTransaction;
private WindowContainerToken mFragmentWindowToken;
@Before @Before
public void setup() { public void setup() {
mController = mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController; mController = mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
mOrganizer = new TaskFragmentOrganizer(Runnable::run); mOrganizer = new TaskFragmentOrganizer(Runnable::run);
mIOrganizer = mOrganizer.getIOrganizer(); mIOrganizer = mOrganizer.getIOrganizer();
mTaskFragment = mock(TaskFragment.class);
mTaskFragmentInfo = mock(TaskFragmentInfo.class); mTaskFragmentInfo = mock(TaskFragmentInfo.class);
mFragmentToken = new Binder(); mFragmentToken = new Binder();
mTaskFragment =
new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */);
mTransaction = new WindowContainerTransaction();
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
spyOn(mController); spyOn(mController);
spyOn(mOrganizer); spyOn(mOrganizer);
spyOn(mTaskFragment);
doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer(); doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer();
doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo(); doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo();
doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl(); doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl();
@@ -190,4 +204,156 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
verify(mOrganizer).onTaskFragmentError(eq(errorCallbackToken), eq(exception)); verify(mOrganizer).onTaskFragmentError(eq(errorCallbackToken), eq(exception));
} }
@Test
public void testWindowContainerTransaction_setTaskFragmentOrganizer() {
mOrganizer.applyTransaction(mTransaction);
assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer());
mTransaction = new WindowContainerTransaction();
mOrganizer.applySyncTransaction(
mTransaction, mock(WindowContainerTransactionCallback.class));
assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer());
}
@Test
public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment()
throws RemoteException {
mOrganizer.applyTransaction(mTransaction);
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
assertThrows(SecurityException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
} catch (RemoteException e) {
fail();
}
});
// Allow transaction to change a TaskFragment created by the organizer.
mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
}
@Test
public void testApplyTransaction_enforceHierarchyChange_reorder() throws RemoteException {
mOrganizer.applyTransaction(mTransaction);
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
mTransaction.reorder(mFragmentWindowToken, true /* onTop */);
assertThrows(SecurityException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
} catch (RemoteException e) {
fail();
}
});
// Allow transaction to change a TaskFragment created by the organizer.
mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
}
@Test
public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment()
throws RemoteException {
mOrganizer.applyTransaction(mTransaction);
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
mTransaction.deleteTaskFragment(mFragmentWindowToken);
assertThrows(SecurityException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
} catch (RemoteException e) {
fail();
}
});
// Allow transaction to change a TaskFragment created by the organizer.
mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
}
@Test
public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
throws RemoteException {
final TaskFragment taskFragment2 =
new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
mOrganizer.applyTransaction(mTransaction);
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
mTransaction.setAdjacentRoots(mFragmentWindowToken, token2);
assertThrows(SecurityException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
} catch (RemoteException e) {
fail();
}
});
// Allow transaction to change a TaskFragment created by the organizer.
mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
taskFragment2.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
}
@Test
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
mOrganizer.applyTransaction(mTransaction);
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
mTransaction.createTaskFragment(mock(TaskFragmentCreationParams.class));
mTransaction.startActivityInTaskFragment(
mFragmentToken, new Intent(), null /* activityOptions */);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
// It is expected to fail for the mock TaskFragmentCreationParams. It is ok as we are
// testing the security check here.
assertThrows(IllegalArgumentException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
} catch (RemoteException e) {
fail();
}
});
}
@Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren()
throws RemoteException {
mOrganizer.applyTransaction(mTransaction);
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */);
assertThrows(SecurityException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
} catch (RemoteException e) {
fail();
}
});
// Allow transaction to change a TaskFragment created by the organizer.
mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
}
} }