Add TaskFragmentOrganizer#applyTransaction with request transition

Instead of having WM Core to "guess" when to request transtiion, let the
organizer to tell whether or not it needs to be applied immediately.

With the shouldApplyIndependently parameter, we can make sure the future
runtime API to change split layout won't affect other ongoing
transition.

Bug: 207070762
Test: atest WmTests:TaskFragmentOrganizerControllerTest
Change-Id: I658b0ba1ae9decc741f09cb53bfff2c45ea076a0
This commit is contained in:
Chris Li
2022-08-22 12:04:37 +08:00
parent 907c1ad88d
commit dc7cb3659d
11 changed files with 362 additions and 212 deletions

View File

@@ -57,6 +57,12 @@ interface ITaskFragmentOrganizerController {
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*/
void onTransactionHandled(in ITaskFragmentOrganizer organizer, in IBinder transactionToken,
in WindowContainerTransaction wct);
void onTransactionHandled(in IBinder transactionToken, in WindowContainerTransaction wct,
int transitionType, boolean shouldApplyIndependently);
/**
* Requests the server to apply the given {@link WindowContainerTransaction}.
*/
void applyTransaction(in WindowContainerTransaction wct, int transitionType,
boolean shouldApplyIndependently);
}

View File

@@ -16,17 +16,25 @@
package android.window;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -34,6 +42,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
@@ -168,20 +177,110 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* {@link #onTransactionReady(TaskFragmentTransaction)}
* @param wct {@link WindowContainerTransaction} that the server should apply for
* update of the transaction.
* @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission
* requirement.
* @param transitionType {@link WindowManager.TransitionType} if it needs to start a
* transition.
* @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new
* transition, which will be queued until the sync engine is
* free if there is any other active sync. If {@code false},
* the {@code wct} will be directly applied to the active sync.
* @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission
* for permission enforcement.
* @hide
*/
public void onTransactionHandled(@NonNull IBinder transactionToken,
@NonNull WindowContainerTransaction wct) {
@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
wct.setTaskFragmentOrganizer(mInterface);
try {
getController().onTransactionHandled(mInterface, transactionToken, wct);
getController().onTransactionHandled(transactionToken, wct, transitionType,
shouldApplyIndependently);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Routes to {@link ITaskFragmentOrganizerController#applyTransaction} instead of
* {@link IWindowOrganizerController#applyTransaction} for the different transition options.
*
* @see #applyTransaction(WindowContainerTransaction, int, boolean, boolean)
*/
@Override
public void applyTransaction(@NonNull WindowContainerTransaction wct) {
// TODO(b/207070762) doing so to keep CTS compatibility. Remove in the next release.
applyTransaction(wct, getTransitionType(wct), false /* shouldApplyIndependently */);
}
/**
* Requests the server to apply the given {@link WindowContainerTransaction}.
*
* @param wct {@link WindowContainerTransaction} to apply.
* @param transitionType {@link WindowManager.TransitionType} if it needs to start a
* transition.
* @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new
* transition, which will be queued until the sync engine is
* free if there is any other active sync. If {@code false},
* the {@code wct} will be directly applied to the active sync.
* @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission
* for permission enforcement.
* @hide
*/
public void applyTransaction(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
if (wct.isEmpty()) {
return;
}
wct.setTaskFragmentOrganizer(mInterface);
try {
getController().applyTransaction(wct, transitionType, shouldApplyIndependently);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the default {@link WindowManager.TransitionType} based on the requested
* {@link WindowContainerTransaction}.
* @hide
*/
// TODO(b/207070762): let Extensions to set the transition type instead.
@WindowManager.TransitionType
public static int getTransitionType(@NonNull WindowContainerTransaction wct) {
if (wct.isEmpty()) {
return TRANSIT_NONE;
}
for (WindowContainerTransaction.Change change : wct.getChanges().values()) {
if ((change.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
// Treat as TRANSIT_CHANGE when there is TaskFragment resizing.
return TRANSIT_CHANGE;
}
}
boolean containsCreatingTaskFragment = false;
boolean containsDeleteTaskFragment = false;
final List<WindowContainerTransaction.HierarchyOp> ops = wct.getHierarchyOps();
for (int i = ops.size() - 1; i >= 0; i--) {
final int type = ops.get(i).getType();
if (type == HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT) {
// Treat as TRANSIT_CHANGE when there is activity reparent.
return TRANSIT_CHANGE;
}
if (type == HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT) {
containsCreatingTaskFragment = true;
} else if (type == HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT) {
containsDeleteTaskFragment = true;
}
}
if (containsCreatingTaskFragment) {
return TRANSIT_OPEN;
}
if (containsDeleteTaskFragment) {
return TRANSIT_CLOSE;
}
// Use TRANSIT_CHANGE as default.
return TRANSIT_CHANGE;
}
/**
* Called when a TaskFragment is created and organized by this organizer.
*
@@ -424,22 +523,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
}
// Notify the server, and the server should apply the WindowContainerTransaction.
onTransactionHandled(transaction.getTransactionToken(), wct);
}
@Override
public void applyTransaction(@NonNull WindowContainerTransaction t) {
t.setTaskFragmentOrganizer(mInterface);
super.applyTransaction(t);
}
// Suppress the lint because it is not a registration method.
@SuppressWarnings("ExecutorRegistration")
@Override
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
t.setTaskFragmentOrganizer(mInterface);
return super.applySyncTransaction(t, callback);
onTransactionHandled(transaction.getTransactionToken(), wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
}
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {

View File

@@ -752,10 +752,8 @@ public final class WindowContainerTransaction implements Parcelable {
* @hide
*/
@NonNull
WindowContainerTransaction setTaskFragmentOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
if (mTaskFragmentOrganizer != null) {
throw new IllegalStateException("Can't set multiple organizers for one transaction.");
}
public WindowContainerTransaction setTaskFragmentOrganizer(
@NonNull ITaskFragmentOrganizer organizer) {
mTaskFragmentOrganizer = organizer;
return this;
}

View File

@@ -1801,6 +1801,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
"-353495930": {
"message": "TaskFragmentTransaction changes are not collected in transition because there is an ongoing sync for applySyncTransaction().",
"level": "WARN",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/WindowOrganizerController.java"
},
"-347866078": {
"message": "Setting move animation on %s",
"level": "VERBOSE",
@@ -2485,6 +2491,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowState.java"
},
"286170861": {
"message": "Creating Pending Transition for TaskFragment: %s",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/WindowOrganizerController.java"
},
"288485303": {
"message": "Attempted to set remove mode to a display that does not exist: %d",
"level": "WARN",

View File

@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -109,7 +110,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
private final Handler mHandler;
private final Object mLock = new Object();
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
public SplitController() {
@@ -209,8 +210,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
// Notify the server, and the server should apply the WindowContainerTransaction.
mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct);
// Notify the server, and the server should apply and merge the
// WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
getTransitionType(wct), false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
}
}
@@ -221,6 +224,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is created.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
@@ -245,6 +251,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is changed.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
@@ -430,6 +439,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* transaction operation.
* @param exception exception from the server side.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@@ -869,23 +881,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Called when we have been waiting too long for the TaskFragment to become non-empty after
* creation.
*/
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
onTaskFragmentAppearEmptyTimeout(wct, container);
mPresenter.applyTransaction(wct);
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
onTaskFragmentAppearEmptyTimeout(wct, container);
// Can be applied independently as a timeout callback.
mPresenter.applyTransaction(wct, getTransitionType(wct),
true /* shouldApplyIndependently */);
}
/**
* Called when we have been waiting too long for the TaskFragment to become non-empty after
* creation.
*/
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
synchronized (mLock) {
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
/**
@@ -1714,7 +1726,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
SplitController.this.onActivityCreated(wct, activity);
mPresenter.applyTransaction(wct);
// The WCT should be applied and merged to the activity launch transition.
mPresenter.applyTransaction(wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
}
}
@@ -1723,7 +1737,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
SplitController.this.onActivityConfigurationChanged(wct, activity);
mPresenter.applyTransaction(wct);
// The WCT should be applied and merged to the Task change transition so that the
// placeholder is launched in the same transition.
mPresenter.applyTransaction(wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
}
}
@@ -1775,7 +1792,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
taskId, intent, launchingActivity);
if (launchedInTaskFragment != null) {
mPresenter.applyTransaction(wct);
// Make sure the WCT is applied immediately instead of being queued so that the
// TaskFragment will be ready before activity attachment.
mPresenter.applyTransaction(wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,

View File

@@ -28,6 +28,7 @@ import android.util.Size;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -251,6 +252,7 @@ class TaskFragmentContainer {
return mInfo;
}
@GuardedBy("mController.mLock")
void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
@@ -258,10 +260,12 @@ class TaskFragmentContainer {
// it is still empty after timeout.
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
mAppearEmptyTimeout = () -> {
mAppearEmptyTimeout = null;
// Call without the pass-in wct when timeout. We need to applyWct directly
// in this case.
mController.onTaskFragmentAppearEmptyTimeout(this);
synchronized (mController.mLock) {
mAppearEmptyTimeout = null;
// Call without the pass-in wct when timeout. We need to applyWct directly
// in this case.
mController.onTaskFragmentAppearEmptyTimeout(this);
}
};
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
} else {

View File

@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
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.verify;
@@ -33,7 +32,6 @@ import static org.mockito.Mockito.never;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
@@ -67,10 +65,7 @@ public class JetpackTaskFragmentOrganizerTest {
private WindowContainerTransaction mTransaction;
@Mock
private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
@Mock
private SplitController mSplitController;
@Mock
private Handler mHandler;
private JetpackTaskFragmentOrganizer mOrganizer;
@Before
@@ -78,8 +73,9 @@ public class JetpackTaskFragmentOrganizerTest {
MockitoAnnotations.initMocks(this);
mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
mOrganizer.registerOrganizer();
mSplitController = new SplitController();
spyOn(mOrganizer);
doReturn(mHandler).when(mSplitController).getHandler();
spyOn(mSplitController);
}
@Test

View File

@@ -127,7 +127,7 @@ public class SplitControllerTest {
mSplitPresenter = mSplitController.mPresenter;
spyOn(mSplitController);
spyOn(mSplitPresenter);
doNothing().when(mSplitPresenter).applyTransaction(any());
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
@@ -1000,7 +1000,8 @@ public class SplitControllerTest {
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentAppeared(any(), eq(info));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
@Test
@@ -1014,7 +1015,8 @@ public class SplitControllerTest {
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentInfoChanged(any(), eq(info));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
@Test
@@ -1028,7 +1030,8 @@ public class SplitControllerTest {
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentVanished(any(), eq(info));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
@Test
@@ -1043,7 +1046,8 @@ public class SplitControllerTest {
verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID),
eq(taskConfig));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
@Test
@@ -1062,7 +1066,8 @@ public class SplitControllerTest {
verify(mSplitController).onTaskFragmentError(any(), eq(errorToken), eq(info), eq(opType),
eq(exception));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
@Test
@@ -1078,7 +1083,8 @@ public class SplitControllerTest {
verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent),
eq(activityToken));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
/** Creates a mock activity in the organizer process. */

View File

@@ -45,6 +45,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
@@ -484,16 +485,31 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
@Override
public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer,
@NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) {
public void onTransactionHandled(@NonNull IBinder transactionToken,
@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
// Keep the calling identity to avoid unsecure change.
mWindowOrganizerController.applyTransaction(wct);
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
applyTransaction(wct, transitionType, shouldApplyIndependently);
final TaskFragmentOrganizerState state = validateAndGetState(
wct.getTaskFragmentOrganizer());
state.onTransactionFinished(transactionToken);
}
}
@Override
public void applyTransaction(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
if (wct.isEmpty()) {
return;
}
mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
shouldApplyIndependently);
}
}
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
* {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.

View File

@@ -74,6 +74,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
@@ -176,7 +177,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applyTransaction");
}
enforceTaskPermission("applyTransaction()", t);
enforceTaskPermission("applyTransaction()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
@@ -194,7 +195,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
}
enforceTaskPermission("applySyncTransaction()", t);
enforceTaskPermission("applySyncTransaction()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
@@ -373,6 +374,87 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
/**
* Applies the {@link WindowContainerTransaction} as a request from
* {@link android.window.TaskFragmentOrganizer}.
*
* @param wct {@link WindowContainerTransaction} to apply.
* @param type {@link WindowManager.TransitionType} if it needs to start a new transition.
* @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new
* transition, which will be queued until the sync engine is
* free if there is any other active sync. If {@code false},
* the {@code wct} will be directly applied to the active sync.
*/
void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
if (!isValidTransaction(wct)) {
return;
}
enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
Objects.requireNonNull(wct));
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
if (mTransitionController.getTransitionPlayer() == null) {
// No need to worry about transition when Shell transition is not enabled.
applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
return;
}
if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
// Sync is for either transition or applySyncTransaction(). We don't support
// multiple sync at the same time because it may cause conflict.
// Create a new transition when there is no active sync to collect the changes.
final Transition transition = mTransitionController.createTransition(type);
applyTransaction(wct, -1 /* syncId */, transition, caller);
mTransitionController.requestStartTransition(transition, null /* startTask */,
null /* remoteTransition */, null /* displayChange */);
return;
}
if (!shouldApplyIndependently) {
// Although there is an active sync, we want to apply the transaction now.
if (!mTransitionController.isCollecting()) {
// This should rarely happen, and we should try to avoid using
// {@link #applySyncTransaction} with Shell transition.
// We still want to apply and merge the transaction to the active sync
// because {@code shouldApplyIndependently} is {@code false}.
ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"TaskFragmentTransaction changes are not collected in transition"
+ " because there is an ongoing sync for"
+ " applySyncTransaction().");
}
// TODO(b/207070762) make sure changes are all collected.
applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
return;
}
// It is ok to queue the WCT until the sync engine is free.
final Transition nextTransition = new Transition(type, 0 /* flags */,
mTransitionController, mService.mWindowManager.mSyncEngine);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Transition for TaskFragment: %s", nextTransition);
mService.mWindowManager.mSyncEngine.queueSyncSet(
// Make sure to collect immediately to prevent another transition
// from sneaking in before it. Note: moveToCollecting internally
// calls startSyncSet.
() -> mTransitionController.moveToCollecting(nextTransition),
() -> {
if (isValidTransaction(wct)) {
applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
null /* displayChange */);
} else {
nextTransition.abort();
}
});
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller) {
applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
@@ -387,12 +469,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller,
@Nullable Transition finishTransition) {
if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
.isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+ " is no longer registered");
return;
}
int effects = 0;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
@@ -1488,25 +1564,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.enforceTaskPermission(func);
}
private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) {
if (t == null || t.getTaskFragmentOrganizer() == null) {
enforceTaskPermission(func);
return;
private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
.isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
// Transaction from an unregistered organizer should not be applied. This can happen
// when the organizer process died before the transaction is applied.
Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+ " is no longer registered");
return false;
}
// 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);
return true;
}
/**
* 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();
private void enforceTaskFragmentOrganizerPermission(@NonNull String func,
@NonNull ITaskFragmentOrganizer organizer, @NonNull WindowContainerTransaction t) {
// Configuration changes
final Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();

View File

@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -49,8 +50,8 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -78,7 +79,6 @@ import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import androidx.test.filters.SmallTest;
@@ -136,6 +136,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment =
new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */);
mTransaction = new WindowContainerTransaction();
mTransaction.setTaskFragmentOrganizer(mIOrganizer);
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
mDefinition = new RemoteAnimationDefinition();
mErrorToken = new Binder();
@@ -155,11 +156,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
// To prevent it from calling the real server.
doNothing().when(mOrganizer).onTransactionHandled(any(), any());
doNothing().when(mOrganizer).applyTransaction(any(), anyInt(), anyBoolean());
doNothing().when(mOrganizer).onTransactionHandled(any(), any(), anyInt(), anyBoolean());
mController.registerOrganizer(mIOrganizer);
}
@Test
public void testCallTaskFragmentCallbackWithoutRegister_throwsException() {
mController.unregisterOrganizer(mIOrganizer);
doReturn(mTask).when(mTaskFragment).getTask();
assertThrows(IllegalArgumentException.class, () -> mController
@@ -175,8 +181,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnTaskFragmentAppeared() {
mController.registerOrganizer(mIOrganizer);
// No-op when the TaskFragment is not attached.
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
@@ -194,7 +198,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnTaskFragmentInfoChanged() {
mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
// No-op if onTaskFragmentAppeared is not called yet.
@@ -233,8 +236,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnTaskFragmentVanished() {
mController.registerOrganizer(mIOrganizer);
mTaskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
@@ -244,7 +245,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnTaskFragmentVanished_clearUpRemaining() {
mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
// Not trigger onTaskFragmentAppeared.
@@ -270,7 +270,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnTaskFragmentParentInfoChanged() {
mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
mTask.getConfiguration().smallestScreenWidthDp = 10;
@@ -317,7 +316,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
public void testOnTaskFragmentError() {
final Throwable exception = new IllegalArgumentException("Test exception");
mController.registerOrganizer(mIOrganizer);
mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
mErrorToken, null /* taskFragment */, HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
exception);
@@ -332,7 +330,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
mController.registerOrganizer(mIOrganizer);
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final Task task = activity.getTask();
activity.info.applicationInfo.uid = uid;
@@ -375,8 +372,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
mController.registerOrganizer(mIOrganizer);
mOrganizer.applyTransaction(mTransaction);
final Task task = createTask(mDisplayContent);
task.addChild(mTaskFragment, POSITION_TOP);
final ActivityRecord activity = createActivityRecord(task);
@@ -404,7 +399,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertEquals(activity.intent, change.getActivityIntent());
assertNotEquals(activity.token, change.getActivityToken());
mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertEquals(mTaskFragment, activity.getTaskFragment());
// The temporary token can only be used once.
@@ -414,7 +409,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testRegisterRemoteAnimations() {
mController.registerOrganizer(mIOrganizer);
mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
@@ -425,23 +419,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@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);
public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() {
// 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));
@@ -457,10 +435,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment()
throws RemoteException {
mController.registerOrganizer(mIOrganizer);
mOrganizer.applyTransaction(mTransaction);
public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment() {
doReturn(true).when(mTaskFragment).isAttached();
// Throw exception if the transaction is trying to change a window that is not organized by
@@ -486,13 +461,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
throws RemoteException {
mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer);
public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots() {
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.
@@ -513,9 +485,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment()
throws RemoteException {
mController.registerOrganizer(mIOrganizer);
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
@@ -526,11 +496,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class),
null /* options */);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
// Successfully created a TaskFragment.
final TaskFragment taskFragment = mAtm.mWindowOrganizerController
.getTaskFragment(fragmentToken);
final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
assertNotNull(taskFragment);
assertEquals(ownerActivity.getTask(), taskFragment.getTask());
}
@@ -539,7 +508,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
public void testApplyTransaction_enforceTaskFragmentOrganized_startActivityInTaskFragment() {
final Task task = createTask(mDisplayContent);
final ActivityRecord ownerActivity = createActivityRecord(task);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -562,7 +530,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
public void testApplyTransaction_enforceTaskFragmentOrganized_reparentActivityInTaskFragment() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -583,7 +550,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_enforceTaskFragmentOrganized_setAdjacentTaskFragments() {
final Task task = createTask(mDisplayContent);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -623,7 +589,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_enforceTaskFragmentOrganized_requestFocusOnTaskFragment() {
final Task task = createTask(mDisplayContent);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -642,44 +607,38 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
public void testApplyTransaction_createTaskFragment_failForDifferentUid()
throws RemoteException {
mController.registerOrganizer(mIOrganizer);
public void testApplyTransaction_createTaskFragment_failForDifferentUid() {
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final int uid = Binder.getCallingUid();
final IBinder fragmentToken = new Binder();
final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
mOrganizerToken, fragmentToken, activity.token).build();
mOrganizer.applyTransaction(mTransaction);
mTransaction.createTaskFragment(params);
// Fail to create TaskFragment when the task uid is different from caller.
activity.info.applicationInfo.uid = uid;
activity.getTask().effectiveUid = uid + 1;
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
// Fail to create TaskFragment when the task uid is different from owner activity.
activity.info.applicationInfo.uid = uid + 1;
activity.getTask().effectiveUid = uid;
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
// Successfully created a TaskFragment for same uid.
activity.info.applicationInfo.uid = uid;
activity.getTask().effectiveUid = uid;
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
}
@Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren()
throws RemoteException {
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached();
// Throw exception if the transaction is trying to change a window that is not organized by
@@ -699,14 +658,12 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate()
throws RemoteException {
public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -717,15 +674,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity);
clearInvocations(mAtm.mRootWindowContainer);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
}
@Test
public void testApplyTransaction_requestFocusOnTaskFragment() {
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
final Task task = createTask(mDisplayContent);
final IBinder token0 = new Binder();
final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
@@ -750,7 +705,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
final ActivityRecord activityInOtherTask = createActivityRecord(mDefaultDisplay);
mDisplayContent.setFocusedApp(activityInOtherTask);
mTransaction.requestFocusOnTaskFragment(token0);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertEquals(activityInOtherTask, mDisplayContent.mFocusedApp);
@@ -758,7 +713,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
activity0.setState(ActivityRecord.State.PAUSED, "test");
activity1.setState(ActivityRecord.State.RESUMED, "test");
mDisplayContent.setFocusedApp(activity1);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertEquals(activity1, mDisplayContent.mFocusedApp);
@@ -766,28 +721,29 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// has a resumed activity.
activity0.setState(ActivityRecord.State.RESUMED, "test");
mDisplayContent.setFocusedApp(activity1);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertEquals(activity0, mDisplayContent.mFocusedApp);
}
@Test
public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
mController.unregisterOrganizer(mIOrganizer);
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
// Nothing should happen as the organizer is not registered.
assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
mController.registerOrganizer(mIOrganizer);
mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
// Successfully created when the organizer is registered.
assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
}
@Test
@@ -799,13 +755,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allow to start activity in a TaskFragment that is in a PIP Task.
mTransaction.startActivityInTaskFragment(
mFragmentToken, activity.token, new Intent(), null /* activityOptions */)
mFragmentToken, activity.token, new Intent(), null /* activityOptions */)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(),
any(), any(), anyInt(), anyInt(), any());
verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment),
eq(HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT),
any(IllegalArgumentException.class));
@@ -820,7 +776,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allow to reparent activity to a TaskFragment that is in a PIP Task.
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment),
@@ -836,9 +792,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allow to set adjacent on a TaskFragment that is in a PIP Task.
mTransaction.setAdjacentTaskFragments(mFragmentToken, null /* fragmentToken2 */,
null /* options */)
null /* options */)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment),
@@ -849,7 +805,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testTaskFragmentInPip_createTaskFragment() {
mController.registerOrganizer(mIOrganizer);
final Task pipTask = createTask(mDisplayContent, WINDOWING_MODE_PINNED,
ACTIVITY_TYPE_STANDARD);
final ActivityRecord activity = createActivityRecord(pipTask);
@@ -859,7 +814,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allow to create TaskFragment in a PIP Task.
createTaskFragmentFromOrganizer(mTransaction, activity, fragmentToken);
mTransaction.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(null), eq(HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT),
@@ -875,7 +830,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allow to delete a TaskFragment that is in a PIP Task.
mTransaction.deleteTaskFragment(mFragmentWindowToken)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment), eq(HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT),
@@ -885,7 +840,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Allow organizer to delete empty TaskFragment for cleanup.
final Task task = mTaskFragment.getTask();
mTaskFragment.removeChild(mTaskFragment.getTopMostActivity());
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertNull(mWindowOrganizerController.getTaskFragment(mFragmentToken));
assertNull(task.getTopChild());
@@ -916,7 +871,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
doReturn(false).when(task).shouldBeVisible(any());
// Sending events
mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -942,7 +896,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
taskFragment.setResumedActivity(null, "test");
// Sending events
mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -977,7 +930,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertTrue(parentTask.shouldBeVisible(null));
// Dispatch pending info changed event from creating the activity
mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -1013,7 +965,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertTrue(task.shouldBeVisible(null));
// Dispatch pending info changed event from creating the activity
mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -1039,13 +990,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
* {@link WindowOrganizerController}.
*/
@Test
public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment()
throws RemoteException {
mController.registerOrganizer(mIOrganizer);
public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
assertNotNull(taskFragment);
@@ -1060,9 +1009,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
* its parent bounds.
*/
@Test
public void testUntrustedEmbedding_configChange() throws RemoteException {
mController.registerOrganizer(mIOrganizer);
mOrganizer.applyTransaction(mTransaction);
public void testUntrustedEmbedding_configChange() {
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
"Test:TaskFragmentOrganizer" /* processName */);
doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode();
@@ -1123,8 +1070,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Make minWidth/minHeight exceeds the TaskFragment bounds.
activity.info.windowLayout = new ActivityInfo.WindowLayout(
0, 0, 0, 0, 0, mTaskFragBounds.width() + 10, mTaskFragBounds.height() + 10);
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -1137,7 +1082,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// minimum dimensions.
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
@@ -1148,8 +1093,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testMinDimensionViolation_ReparentChildren() {
final Task task = createTask(mDisplayContent);
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
final IBinder oldFragToken = new Binder();
final TaskFragment oldTaskFrag = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
@@ -1175,7 +1118,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTransaction.reparentChildren(oldTaskFrag.mRemoteToken.toWindowContainerToken(),
mTaskFragment.mRemoteToken.toWindowContainerToken())
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
@@ -1186,8 +1129,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testMinDimensionViolation_SetBounds() {
final Task task = createTask(mDisplayContent);
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.createActivityCount(1)
@@ -1206,7 +1147,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// minimum dimensions.
mTransaction.setBounds(mTaskFragment.mRemoteToken.toWindowContainerToken(), mTaskFragBounds)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
assertApplyTransactionAllowed(mTransaction);
assertWithMessage("setBounds must not be performed.")
.that(mTaskFragment.getBounds()).isEqualTo(task.getBounds());
@@ -1214,18 +1155,17 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnTransactionReady_invokeOnTransactionHandled() {
mController.registerOrganizer(mIOrganizer);
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
mOrganizer.onTransactionReady(transaction);
// Organizer should always trigger #onTransactionHandled when receives #onTransactionReady
verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any());
verify(mOrganizer, never()).applyTransaction(any());
verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
verify(mOrganizer, never()).applyTransaction(any(), anyInt(), anyBoolean());
}
@Test
public void testDispatchTransaction_deferTransitionReady() {
mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class);
final ArgumentCaptor<WindowContainerTransaction> wctCaptor =
@@ -1238,12 +1178,15 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Defer transition when send TaskFragment transaction during transition collection.
verify(mTransitionController).deferTransitionReady();
verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture());
verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture(),
anyInt(), anyBoolean());
mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue());
final IBinder transactionToken = tokenCaptor.getValue();
final WindowContainerTransaction wct = wctCaptor.getValue();
wct.setTaskFragmentOrganizer(mIOrganizer);
mController.onTransactionHandled(transactionToken, wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
// Apply the organizer change and continue transition.
verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue());
verify(mTransitionController).continueTransitionReady();
}
@@ -1258,7 +1201,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
ownerActivity.getTask().effectiveUid = uid;
final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
mOrganizerToken, fragmentToken, ownerActivity.token).build();
mOrganizer.applyTransaction(wct);
wct.setTaskFragmentOrganizer(mIOrganizer);
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
wct.createTaskFragment(params);
@@ -1266,22 +1209,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
/** Asserts that applying the given transaction will throw a {@link SecurityException}. */
private void assertApplyTransactionDisallowed(WindowContainerTransaction t) {
assertThrows(SecurityException.class, () -> {
try {
mAtm.getWindowOrganizerController().applyTransaction(t);
} catch (RemoteException e) {
fail();
}
});
assertThrows(SecurityException.class, () ->
mController.applyTransaction(t, getTransitionType(t),
false /* shouldApplyIndependently */));
}
/** Asserts that applying the given transaction will not throw any exception. */
private void assertApplyTransactionAllowed(WindowContainerTransaction t) {
try {
mAtm.getWindowOrganizerController().applyTransaction(t);
} catch (RemoteException e) {
fail();
}
mController.applyTransaction(t, getTransitionType(t), false /* shouldApplyIndependently */);
}
/** Asserts that there will be a transaction for TaskFragment appeared. */
@@ -1367,8 +1302,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
/** Setups an embedded TaskFragment in a PIP Task. */
private void setupTaskFragmentInPip() {
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setCreateParentTask()
.setFragmentToken(mFragmentToken)
@@ -1376,8 +1309,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.createActivityCount(1)
.build();
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
mAtm.mWindowOrganizerController.mLaunchTaskFragments
.put(mFragmentToken, mTaskFragment);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
mTaskFragment.getTask().setWindowingMode(WINDOWING_MODE_PINNED);
}