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 {
ctor public WindowOrganizer();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) 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 int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
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 {

View File

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

View File

@@ -120,6 +120,19 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
public void onTaskFragmentError(
@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() {
@Override
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() {
try {
return getWindowOrganizerController().getTaskOrganizerController();

View File

@@ -35,6 +35,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 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
private IBinder mErrorCallbackToken;
@Nullable
private ITaskFragmentOrganizer mTaskFragmentOrganizer;
public WindowContainerTransaction() {}
private WindowContainerTransaction(Parcel in) {
in.readMap(mChanges, null /* loader */);
in.readList(mHierarchyOps, null /* loader */);
mErrorCallbackToken = in.readStrongBinder();
mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
}
private Change getOrCreateChange(IBinder token) {
@@ -473,7 +478,7 @@ public final class WindowContainerTransaction implements Parcelable {
final HierarchyOp hierarchyOp =
new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN)
.setContainer(oldParent.asBinder())
.setReparentContainer(newParent.asBinder())
.setReparentContainer(newParent != null ? newParent.asBinder() : null)
.build();
mHierarchyOps.add(hierarchyOp);
return this;
@@ -496,6 +501,23 @@ public final class WindowContainerTransaction implements Parcelable {
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.
* @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
!= 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
@@ -547,11 +579,21 @@ public final class WindowContainerTransaction implements Parcelable {
return mErrorCallbackToken;
}
/** @hide */
@Nullable
public ITaskFragmentOrganizer getTaskFragmentOrganizer() {
return mTaskFragmentOrganizer;
}
@Override
@NonNull
public String toString() {
return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps
+ " errorCallbackToken=" + mErrorCallbackToken + " }";
return "WindowContainerTransaction {"
+ " changes = " + mChanges
+ " hops = " + mHierarchyOps
+ " errorCallbackToken=" + mErrorCallbackToken
+ " taskFragmentOrganizer=" + mTaskFragmentOrganizer
+ " }";
}
@Override
@@ -560,6 +602,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeMap(mChanges);
dest.writeList(mHierarchyOps);
dest.writeStrongBinder(mErrorCallbackToken);
dest.writeStrongInterface(mTaskFragmentOrganizer);
}
@Override

View File

@@ -36,9 +36,16 @@ public class WindowOrganizer {
/**
* 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.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
conditional = true)
public void applyTransaction(@NonNull WindowContainerTransaction t) {
try {
if (!t.isEmpty()) {
@@ -51,6 +58,12 @@ public class WindowOrganizer {
/**
* 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 callback This transaction will use the synchronization scheme described in
* 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.
* 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,
@NonNull WindowContainerTransactionCallback callback) {
try {
@@ -123,7 +137,6 @@ public class WindowOrganizer {
}
}
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
}

View File

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

View File

@@ -289,6 +289,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
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() {
return mAdjacentTaskFragment;
}

View File

@@ -54,6 +54,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController;
import android.window.ITransitionPlayer;
@@ -110,7 +111,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final TransitionController mTransitionController;
/**
* 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<>();
@@ -139,10 +140,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override
public void applyTransaction(WindowContainerTransaction t) {
enforceTaskPermission("applyTransaction()");
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 long ident = Binder.clearCallingIdentity();
try {
@@ -157,10 +158,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override
public int applySyncTransaction(WindowContainerTransaction t,
IWindowContainerTransactionCallback callback) {
enforceTaskPermission("applySyncTransaction()");
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
}
enforceTaskPermission("applySyncTransaction()", t);
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
@@ -620,7 +621,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
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()) {
Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ oldParent);
@@ -906,6 +909,102 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
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) {
final ActivityRecord ownerActivity =
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.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.eq;
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.verify;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -37,8 +41,12 @@ import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import androidx.test.filters.SmallTest;
@@ -61,18 +69,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
private TaskFragment mTaskFragment;
private TaskFragmentInfo mTaskFragmentInfo;
private IBinder mFragmentToken;
private WindowContainerTransaction mTransaction;
private WindowContainerToken mFragmentWindowToken;
@Before
public void setup() {
mController = mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
mOrganizer = new TaskFragmentOrganizer(Runnable::run);
mIOrganizer = mOrganizer.getIOrganizer();
mTaskFragment = mock(TaskFragment.class);
mTaskFragmentInfo = mock(TaskFragmentInfo.class);
mFragmentToken = new Binder();
mTaskFragment =
new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */);
mTransaction = new WindowContainerTransaction();
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
spyOn(mController);
spyOn(mOrganizer);
spyOn(mTaskFragment);
doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer();
doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo();
doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl();
@@ -190,4 +204,156 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
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);
}
}