diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2eafa9309998c..5550f9ab6cba6 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -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 { diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index e6746556fb67d..6758a3b411a2a 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -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; } } - } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 3b4d4e52c908e..b252df7c72e9b 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -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) { diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 8fa011028f44a..6f250fc0ce7a3 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -290,6 +290,7 @@ public class TaskOrganizer extends WindowOrganizer { } }; + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) private ITaskOrganizerController getController() { try { return getWindowOrganizerController().getTaskOrganizerController(); diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 9c512add3345a..8735ed841ebc3 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -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 diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 544d422400791..78dbebaf27387 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -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(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 56095bb9f31f3..e7cca8e33e332 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3442,7 +3442,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public IWindowOrganizerController getWindowOrganizerController() { - enforceTaskPermission("getWindowOrganizerController()"); return mWindowOrganizerController; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 12ad634fb6b80..8b7b06ac67925 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -283,6 +283,12 @@ class TaskFragment extends 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; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4fa3aabaabbca..ffd89d3a94fe6 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -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 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> entries = + t.getChanges().entrySet().iterator(); + while (entries.hasNext()) { + final Map.Entry 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 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()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 6bd8ad27342a4..116778de6b7a7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -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(); @@ -180,4 +194,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); + } }