Allow TaskFragmentOrganizer to apply WCT without permission
TaskFragmentOrganizer will be used by regular apps without the permission to manage Task. We are allow it to apply transactions if it is operating on TaskFragment that is organized by itself. Bug: 193191599 Test: atest WmTests:TaskFragmentOrganizerControllerTest Change-Id: I82e5d49260680bd496ff184c6795ec817c65d858
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -290,6 +290,7 @@ public class TaskOrganizer extends WindowOrganizer {
|
||||
}
|
||||
};
|
||||
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
|
||||
private ITaskOrganizerController getController() {
|
||||
try {
|
||||
return getWindowOrganizerController().getTaskOrganizerController();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -3442,7 +3442,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
|
||||
|
||||
@Override
|
||||
public IWindowOrganizerController getWindowOrganizerController() {
|
||||
enforceTaskPermission("getWindowOrganizerController()");
|
||||
return mWindowOrganizerController;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +283,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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user