diff options
author | 2022-08-24 01:11:14 +0000 | |
---|---|---|
committer | 2022-08-24 01:11:14 +0000 | |
commit | 570d249c9cf1e6ec04edadc2323bd561abd2dcc6 (patch) | |
tree | f600f561a29884b2e1425421151e1555258afa53 | |
parent | 0859d1505d69347e3c3232ce7fd2830adcad1630 (diff) | |
parent | 3294ef42e4f0d7a1b2472ab22b9374f0a2d05561 (diff) |
Merge "Wait for TaskFragmentOrganizer to finish handling transaction" into tm-qpr-dev am: 3294ef42e4
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19564031
Change-Id: I55787b4ac6e4018b19ad03d33ca9e86e5ac000e1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
8 files changed, 193 insertions, 43 deletions
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 8407d10bc3ea..884ca77ea377 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -16,8 +16,10 @@ package android.window; +import android.os.IBinder; import android.view.RemoteAnimationDefinition; import android.window.ITaskFragmentOrganizer; +import android.window.WindowContainerTransaction; /** @hide */ interface ITaskFragmentOrganizerController { @@ -46,8 +48,15 @@ interface ITaskFragmentOrganizerController { void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId); /** - * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and - * only occupies a portion of Task bounds. - */ + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + */ boolean isActivityEmbedded(in IBinder activityToken); + + /** + * 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); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index c43cf55ce847..7b6139fbcc2f 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -26,7 +26,6 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.annotation.TestApi; import android.content.Intent; import android.content.res.Configuration; @@ -149,6 +148,28 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** + * Notifies the server that the organizer has finished handling the given transaction. The + * server should apply the given {@link WindowContainerTransaction} for the necessary changes. + * + * @param transactionToken {@link TaskFragmentTransaction#getTransactionToken()} from + * {@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. + * @hide + */ + public void onTransactionHandled(@NonNull IBinder transactionToken, + @NonNull WindowContainerTransaction wct) { + wct.setTaskFragmentOrganizer(mInterface); + try { + getController().onTransactionHandled(mInterface, transactionToken, wct); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Called when a TaskFragment is created and organized by this organizer. * * @param taskFragmentInfo Info of the TaskFragment that is created. @@ -318,12 +339,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { /** * Called when the transaction is ready so that the organizer can update the TaskFragments based * on the changes in transaction. - * Note: {@link WindowOrganizer#applyTransaction} permission requirement is conditional for - * {@link TaskFragmentOrganizer}. - * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission * @hide */ - @SuppressLint("AndroidFrameworkRequiresPermission") public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); @@ -389,8 +406,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { "Unknown TaskFragmentEvent=" + change.getType()); } } - // TODO(b/240519866): notify TaskFragmentOrganizerController that the transition is done. - applyTransaction(wct); + + // Notify the server, and the server should apply the WindowContainerTransaction. + onTransactionHandled(transaction.getTransactionToken(), wct); } @Override diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 07e8e8c473c6..84a5fea9f57f 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.res.Configuration; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; @@ -41,19 +42,31 @@ import java.util.List; */ public final class TaskFragmentTransaction implements Parcelable { + /** Unique token to represent this transaction. */ + private final IBinder mTransactionToken; + + /** Changes in this transaction. */ private final ArrayList<Change> mChanges = new ArrayList<>(); - public TaskFragmentTransaction() {} + public TaskFragmentTransaction() { + mTransactionToken = new Binder(); + } private TaskFragmentTransaction(Parcel in) { + mTransactionToken = in.readStrongBinder(); in.readTypedList(mChanges, Change.CREATOR); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mTransactionToken); dest.writeTypedList(mChanges); } + public IBinder getTransactionToken() { + return mTransactionToken; + } + /** Adds a {@link Change} to this transaction. */ public void addChange(@Nullable Change change) { if (change != null) { @@ -74,7 +87,9 @@ public final class TaskFragmentTransaction implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("TaskFragmentTransaction{changes=["); + sb.append("TaskFragmentTransaction{token="); + sb.append(mTransactionToken); + sb.append(" changes=["); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { sb.append(','); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index e8873cd4a3e7..4976784416ad 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2041,6 +2041,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-108248992": { + "message": "Defer transition ready for TaskFragmentTransaction=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "-106400104": { "message": "Preload recents with %s", "level": "DEBUG", @@ -2089,6 +2095,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "-79016993": { + "message": "Continue transition ready for TaskFragmentTransaction=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "-70719599": { "message": "Unregister remote animations for organizer=%s uid=%d pid=%d", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 88059e1a0d04..d615583f4d7f 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -49,7 +49,9 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; +import android.window.WindowContainerTransaction; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import java.lang.annotation.Retention; @@ -68,6 +70,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ActivityTaskManagerService mAtmService; private final WindowManagerGlobalLock mGlobalLock; + private final WindowOrganizerController mWindowOrganizerController; + /** * A Map which manages the relationship between * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState} @@ -82,9 +86,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ArraySet<Task> mTmpTaskSet = new ArraySet<>(); - TaskFragmentOrganizerController(ActivityTaskManagerService atm) { - mAtmService = atm; + TaskFragmentOrganizerController(@NonNull ActivityTaskManagerService atm, + @NonNull WindowOrganizerController windowOrganizerController) { + mAtmService = requireNonNull(atm); mGlobalLock = atm.mGlobalLock; + mWindowOrganizerController = requireNonNull(windowOrganizerController); } /** @@ -131,6 +137,14 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions = new SparseArray<>(); + /** + * List of {@link TaskFragmentTransaction#getTransactionToken()} that have been sent to the + * organizer. If the transaction is sent during a transition, the + * {@link TransitionController} will wait until the transaction is finished. + * @see #onTransactionFinished(IBinder) + */ + private final List<IBinder> mRunningTransactions = new ArrayList<>(); + TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) { mOrganizer = organizer; mOrganizerPid = pid; @@ -176,6 +190,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr taskFragment.removeImmediately(); mOrganizedTaskFragments.remove(taskFragment); } + for (int i = mRunningTransactions.size() - 1; i >= 0; i--) { + // Cleanup any running transaction to unblock the current transition. + onTransactionFinished(mRunningTransactions.get(i)); + } mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/); } @@ -320,6 +338,40 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setActivityIntent(activity.intent) .setActivityToken(activityToken); } + + void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) { + if (transaction.isEmpty()) { + return; + } + try { + mOrganizer.onTransactionReady(transaction); + } catch (RemoteException e) { + Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); + return; + } + onTransactionStarted(transaction.getTransactionToken()); + } + + /** Called when the transaction is sent to the organizer. */ + void onTransactionStarted(@NonNull IBinder transactionToken) { + if (!mWindowOrganizerController.getTransitionController().isCollecting()) { + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Defer transition ready for TaskFragmentTransaction=%s", transactionToken); + mRunningTransactions.add(transactionToken); + mWindowOrganizerController.getTransitionController().deferTransitionReady(); + } + + /** Called when the transaction is finished. */ + void onTransactionFinished(@NonNull IBinder transactionToken) { + if (!mRunningTransactions.remove(transactionToken)) { + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Continue transition ready for TaskFragmentTransaction=%s", transactionToken); + mWindowOrganizerController.getTransitionController().continueTransitionReady(); + } } @Nullable @@ -336,7 +388,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerOrganizer(ITaskFragmentOrganizer organizer) { + public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -354,7 +406,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void unregisterOrganizer(ITaskFragmentOrganizer organizer) { + public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) { validateAndGetState(organizer); final int pid = Binder.getCallingPid(); final long uid = Binder.getCallingUid(); @@ -372,8 +424,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId, - RemoteAnimationDefinition definition) { + public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId, + @NonNull RemoteAnimationDefinition definition) { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -398,7 +450,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) { + public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) { final int pid = Binder.getCallingPid(); final long uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -416,6 +468,17 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + @Override + public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer, + @NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) { + synchronized (mGlobalLock) { + // Keep the calling identity to avoid unsecure change. + mWindowOrganizerController.applyTransaction(wct); + final TaskFragmentOrganizerState state = validateAndGetState(organizer); + state.onTransactionFinished(transactionToken); + } + } + /** * 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. @@ -775,13 +838,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final int organizerNum = mPendingTaskFragmentEvents.size(); for (int i = 0; i < organizerNum; i++) { - final ITaskFragmentOrganizer organizer = mTaskFragmentOrganizerState.get( - mPendingTaskFragmentEvents.keyAt(i)).mOrganizer; - dispatchPendingEvents(organizer, mPendingTaskFragmentEvents.valueAt(i)); + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(mPendingTaskFragmentEvents.keyAt(i)); + dispatchPendingEvents(state, mPendingTaskFragmentEvents.valueAt(i)); } } - void dispatchPendingEvents(@NonNull ITaskFragmentOrganizer organizer, + void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state, @NonNull List<PendingTaskFragmentEvent> pendingEvents) { if (pendingEvents.isEmpty()) { return; @@ -817,7 +880,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr if (mTmpTaskSet.add(task)) { // Make sure the organizer know about the Task config. transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( - PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, state.mOrganizer) .setTask(task) .build())); } @@ -825,7 +888,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr transaction.addChange(prepareChange(event)); } mTmpTaskSet.clear(); - dispatchTransactionInfo(organizer, transaction); + state.dispatchTransaction(transaction); pendingEvents.removeAll(candidateEvents); } @@ -855,6 +918,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer(); + final TaskFragmentOrganizerState state = validateAndGetState(organizer); final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); // Make sure the organizer know about the Task config. transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( @@ -862,22 +926,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setTask(taskFragment.getTask()) .build())); transaction.addChange(prepareChange(event)); - dispatchTransactionInfo(event.mTaskFragmentOrg, transaction); + state.dispatchTransaction(transaction); mPendingTaskFragmentEvents.get(organizer.asBinder()).remove(event); } - private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer, - @NonNull TaskFragmentTransaction transaction) { - if (transaction.isEmpty()) { - return; - } - try { - organizer.onTransactionReady(transaction); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); - } - } - @Nullable private TaskFragmentTransaction.Change prepareChange( @NonNull PendingTaskFragmentEvent event) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 803890b4032d..2d3e437bed60 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1815,6 +1815,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** This undoes one call to {@link #deferTransitionReady}. */ void continueTransitionReady() { --mReadyTracker.mDeferReadyDepth; + // Apply ready in case it is waiting for the previous defer call. + applyReady(); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4f03264b1556..68b1d354272d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -147,7 +147,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); - mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm); + mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm, this); } void setWindowManager(WindowManagerService wms) { 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 da72030b313d..9274eb3f1490 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -25,6 +25,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; @@ -46,7 +47,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -65,6 +65,7 @@ import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizerToken; +import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; @@ -90,6 +91,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private TaskFragmentOrganizerController mController; private WindowOrganizerController mWindowOrganizerController; + private TransitionController mTransitionController; private TaskFragmentOrganizer mOrganizer; private TaskFragmentOrganizerToken mOrganizerToken; private ITaskFragmentOrganizer mIOrganizer; @@ -107,9 +109,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private Task mTask; @Before - public void setup() { + public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); mWindowOrganizerController = mAtm.mWindowOrganizerController; + mTransitionController = mWindowOrganizerController.mTransitionController; mController = mWindowOrganizerController.mTaskFragmentOrganizerController; mOrganizer = new TaskFragmentOrganizer(Runnable::run); mOrganizerToken = mOrganizer.getOrganizerToken(); @@ -128,11 +131,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { spyOn(mController); spyOn(mOrganizer); spyOn(mTaskFragment); + spyOn(mWindowOrganizerController); + spyOn(mTransitionController); doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer(); doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo(); doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl(); doReturn(mFragmentToken).when(mTaskFragment).getFragmentToken(); doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); + + // To prevent it from calling the real server. + doNothing().when(mOrganizer).onTransactionHandled(any(), any()); } @Test @@ -866,7 +874,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertFalse(parentTask.shouldBeVisible(null)); // Verify the info changed callback still occurred despite the task being invisible - reset(mOrganizer); + clearInvocations(mOrganizer); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); @@ -899,7 +907,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); // Verify the info changed callback is not called when the task is invisible - reset(mOrganizer); + clearInvocations(mOrganizer); doReturn(false).when(task).shouldBeVisible(any()); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); @@ -1092,6 +1100,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .that(mTaskFragment.getBounds()).isEqualTo(task.getBounds()); } + @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()); + } + + @Test + public void testDispatchTransaction_deferTransitionReady() { + mController.registerOrganizer(mIOrganizer); + setupMockParent(mTaskFragment, mTask); + final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class); + final ArgumentCaptor<WindowContainerTransaction> wctCaptor = + ArgumentCaptor.forClass(WindowContainerTransaction.class); + doReturn(true).when(mTransitionController).isCollecting(); + + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + // Defer transition when send TaskFragment transaction during transition collection. + verify(mTransitionController).deferTransitionReady(); + verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture()); + + mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue()); + + // Apply the organizer change and continue transition. + verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue()); + verify(mTransitionController).continueTransitionReady(); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction} to apply the transaction, |