diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 884ca77ea3774..3250dd8f7308f 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -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); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 19b1374ae767d..649785a80f435 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -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 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() { diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 9ebdea82593be..ffbdf08e99dc2 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -763,10 +763,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; } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 795e0bd6d23ac..f6499f87dd41d 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -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", @@ -2491,6 +2497,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", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 4102732fd80eb..5727b91376d92 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -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> mEmbeddingCallback; private final List 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, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 344ffc759c46a..2843b145cb3ea 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -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 { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index d0eaf34274aad..58a627bafa161 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -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 diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index f7436108d3e3b..4dbbc04f94449 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -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. */ diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index d8a054cf45fab..8c037a7390b15 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -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. diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 5fefc3762c0d3..d34ad7d796958 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -75,6 +75,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; @@ -177,7 +178,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 { @@ -195,7 +196,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 { @@ -374,6 +375,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 */); @@ -388,12 +470,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(); @@ -1495,25 +1571,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> entries = t.getChanges().entrySet().iterator(); 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 24cdc0fb329fc..9bdf750767b3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -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 tokenCaptor = ArgumentCaptor.forClass(IBinder.class); final ArgumentCaptor 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); }