Merge "Handle TaskFragmentOrganizer#onTransactionReady in sync" into tm-qpr-dev am: e102950286

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19691944

Change-Id: I7ad36e326cfd5bf10c1be2dff079e49a9afcf891
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chris Li
2022-08-27 10:29:13 +00:00
committed by Automerger Merge Worker
7 changed files with 582 additions and 285 deletions

View File

@@ -47,11 +47,24 @@ import java.util.concurrent.Executor;
public class TaskFragmentOrganizer extends WindowOrganizer { public class TaskFragmentOrganizer extends WindowOrganizer {
/** /**
* Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}. * Key to the {@link Throwable} in {@link TaskFragmentTransaction.Change#getErrorBundle()}.
* @hide
*/ */
private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception"; public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable";
private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type"; /**
* Key to the {@link TaskFragmentInfo} in
* {@link TaskFragmentTransaction.Change#getErrorBundle()}.
* @hide
*/
public static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
/**
* Key to the {@link WindowContainerTransaction.HierarchyOp} in
* {@link TaskFragmentTransaction.Change#getErrorBundle()}.
* @hide
*/
public static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
/** /**
* Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any) * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any)
@@ -61,7 +74,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception, public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
@Nullable TaskFragmentInfo info, int opType) { @Nullable TaskFragmentInfo info, int opType) {
final Bundle errorBundle = new Bundle(); final Bundle errorBundle = new Bundle();
errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception); errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception);
if (info != null) { if (info != null) {
errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info); errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
} }
@@ -342,6 +355,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* @hide * @hide
*/ */
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
// TODO(b/240519866): move to SplitController#onTransactionReady to make sure the whole
// transaction is handled in one sync block. Keep the implementation below to keep CTS
// compatibility. Remove in the next release.
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) { for (TaskFragmentTransaction.Change change : changes) {
@@ -391,7 +407,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
errorBundle.getParcelable( errorBundle.getParcelable(
KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class), KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE), errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION, errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE,
java.lang.Throwable.class)); java.lang.Throwable.class));
break; break;
case TYPE_ACTIVITY_REPARENTED_TO_TASK: case TYPE_ACTIVITY_REPARENTED_TO_TASK:

View File

@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity; import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode; import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -29,6 +28,7 @@ import android.util.ArrayMap;
import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -62,18 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* Callback that notifies the controller about changes to task fragments. * Callback that notifies the controller about changes to task fragments.
*/ */
interface TaskFragmentCallback { interface TaskFragmentCallback {
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, void onTransactionReady(@NonNull TaskFragmentTransaction transaction);
@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Configuration parentConfig);
void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken);
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
} }
/** /**
@@ -270,50 +259,16 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken()); wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
} }
@Override void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
@NonNull TaskFragmentInfo taskFragmentInfo) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
} }
@Override void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo);
}
@Override
public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
} }
@Override @Override
public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
int taskId, @NonNull Configuration parentConfig) { mCallback.onTransactionReady(transaction);
mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig);
}
@Override
public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken);
}
@Override
public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@NonNull IBinder errorCallbackToken,
@Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {
if (taskFragmentInfo != null) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
}
mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType);
} }
} }

View File

@@ -19,6 +19,15 @@ package androidx.window.extensions.embedding;
import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 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.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_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -53,6 +62,7 @@ import android.util.Pair;
import android.util.Size; import android.util.Size;
import android.util.SparseArray; import android.util.SparseArray;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
@@ -144,11 +154,78 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
} }
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
*/
@Override @Override
public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
@NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) { synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); final WindowContainerTransaction wct = new WindowContainerTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
final int taskId = change.getTaskId();
final TaskFragmentInfo info = change.getTaskFragmentInfo();
switch (change.getType()) {
case TYPE_TASK_FRAGMENT_APPEARED:
mPresenter.updateTaskFragmentInfo(info);
onTaskFragmentAppeared(wct, info);
break;
case TYPE_TASK_FRAGMENT_INFO_CHANGED:
mPresenter.updateTaskFragmentInfo(info);
onTaskFragmentInfoChanged(wct, info);
break;
case TYPE_TASK_FRAGMENT_VANISHED:
mPresenter.removeTaskFragmentInfo(info);
onTaskFragmentVanished(wct, info);
break;
case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
break;
case TYPE_TASK_FRAGMENT_ERROR:
final Bundle errorBundle = change.getErrorBundle();
final IBinder errorToken = change.getErrorCallbackToken();
final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable(
KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE);
final Throwable exception = errorBundle.getSerializable(
KEY_ERROR_CALLBACK_THROWABLE, Throwable.class);
if (errorTaskFragmentInfo != null) {
mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo);
}
onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType,
exception);
break;
case TYPE_ACTIVITY_REPARENTED_TO_TASK:
onActivityReparentedToTask(
wct,
taskId,
change.getActivityIntent(),
change.getActivityToken());
break;
default:
throw new IllegalArgumentException(
"Unknown TaskFragmentEvent=" + change.getType());
}
}
// Notify the server, and the server should apply the WindowContainerTransaction.
mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct);
updateCallbackIfNecessary();
}
}
/**
* Called when a TaskFragment is created and organized by this organizer.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is created.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) {
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) { if (container == null) {
return; return;
} }
@@ -160,15 +237,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Update with the latest Task configuration. // Update with the latest Task configuration.
updateContainer(wct, container); updateContainer(wct, container);
} }
updateCallbackIfNecessary();
}
} }
@Override /**
public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, * Called when the status of an organized TaskFragment is changed.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is changed.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) { @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) { if (container == null) {
return; return;
} }
@@ -209,16 +290,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// needed. // needed.
updateContainer(wct, container); updateContainer(wct, container);
} }
updateCallbackIfNecessary();
}
} }
@Override /**
public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, * Called when an organized TaskFragment is removed.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is removed.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) { @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
final TaskFragmentContainer container = getContainer(
taskFragmentInfo.getFragmentToken());
if (container != null) { if (container != null) {
// Cleanup if the TaskFragment vanished is not requested by the organizer. // Cleanup if the TaskFragment vanished is not requested by the organizer.
removeContainer(container); removeContainer(container);
@@ -228,16 +312,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (newTopContainer != null) { if (newTopContainer != null) {
updateContainer(wct, newTopContainer); updateContainer(wct, newTopContainer);
} }
updateCallbackIfNecessary();
} }
cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
} }
}
@Override /**
public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, * Called when the parent leaf Task of organized TaskFragments is changed.
* When the leaf Task is changed, the organizer may want to update the TaskFragments in one
* transaction.
*
* For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
* with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
* can be an override bounds.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskId Id of the parent Task that is changed.
* @param parentConfig Config of the parent Task.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Configuration parentConfig) { int taskId, @NonNull Configuration parentConfig) {
synchronized (mLock) {
onTaskConfigurationChanged(taskId, parentConfig); onTaskConfigurationChanged(taskId, parentConfig);
if (isInPictureInPicture(parentConfig)) { if (isInPictureInPicture(parentConfig)) {
// No need to update presentation in PIP until the Task exit PIP. // No need to update presentation in PIP until the Task exit PIP.
@@ -259,15 +354,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
updateContainer(wct, container); updateContainer(wct, container);
} }
} }
updateCallbackIfNecessary();
}
} }
@Override /**
public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, * Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
* original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskId The Task that the activity is reparented to.
* @param activityIntent The intent that the activity is original launched with.
* @param activityToken If the activity belongs to the same process as the organizer, this
* will be the actual activity token; if the activity belongs to a
* different process, the server will generate a temporary token that
* the organizer can use to reparent the activity through
* {@link WindowContainerTransaction} if needed.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent activityIntent, int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) { @NonNull IBinder activityToken) {
synchronized (mLock) {
// If the activity belongs to the current app process, we treat it as a new activity // If the activity belongs to the current app process, we treat it as a new activity
// launch. // launch.
final Activity activity = getActivity(activityToken); final Activity activity = getActivity(activityToken);
@@ -281,7 +389,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// like a normal launch. // like a normal launch.
placeActivityInTopContainer(wct, activity); placeActivityInTopContainer(wct, activity);
} }
updateCallbackIfNecessary();
return; return;
} }
@@ -309,12 +416,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Because the activity does not belong to the organizer process, we wait until // Because the activity does not belong to the organizer process, we wait until
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
} }
}
@Override /**
public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, * Called when the {@link WindowContainerTransaction} created with
@Nullable TaskFragmentInfo taskFragmentInfo, int opType) { * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
synchronized (mLock) { *
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
* TaskFragment created.
* @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
* transaction operation.
* @param exception exception from the server side.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {
Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
switch (opType) { switch (opType) {
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
@@ -332,8 +453,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo); container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities(); container.clearPendingAppearedActivities();
if (container.isEmpty()) { if (container.isEmpty()) {
mPresenter.cleanupContainer(wct, container, mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
false /* shouldFinishDependent */);
} }
break; break;
} }
@@ -342,7 +462,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
+ ", opType = " + opType); + ", opType = " + opType);
} }
} }
}
/** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {

View File

@@ -36,6 +36,7 @@ import android.graphics.Point;
import android.os.Handler; import android.os.Handler;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerToken; import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
@@ -129,6 +130,14 @@ public class JetpackTaskFragmentOrganizerTest {
WINDOWING_MODE_UNDEFINED); WINDOWING_MODE_UNDEFINED);
} }
@Test
public void testOnTransactionReady() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
mOrganizer.onTransactionReady(transaction);
verify(mCallback).onTransactionReady(transaction);
}
private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) {
return new TaskFragmentInfo(container.getTaskFragmentToken(), return new TaskFragmentInfo(container.getTaskFragmentToken(),
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,

View File

@@ -19,6 +19,13 @@ package androidx.window.extensions.embedding;
import static android.app.ActivityManager.START_CANCELED; import static android.app.ActivityManager.START_CANCELED;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
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 androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO; import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
@@ -67,6 +74,8 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@@ -980,6 +989,98 @@ public class SplitControllerTest {
assertTrue(taskContainer.mSplitContainers.isEmpty()); assertTrue(taskContainer.mSplitContainers.isEmpty());
} }
@Test
public void testOnTransactionReady_taskFragmentAppeared() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_APPEARED)
.setTaskId(TASK_ID)
.setTaskFragmentToken(new Binder())
.setTaskFragmentInfo(info));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentAppeared(any(), eq(info));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
}
@Test
public void testOnTransactionReady_taskFragmentInfoChanged() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_INFO_CHANGED)
.setTaskId(TASK_ID)
.setTaskFragmentToken(new Binder())
.setTaskFragmentInfo(info));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentInfoChanged(any(), eq(info));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
}
@Test
public void testOnTransactionReady_taskFragmentVanished() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED)
.setTaskId(TASK_ID)
.setTaskFragmentToken(new Binder())
.setTaskFragmentInfo(info));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentVanished(any(), eq(info));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
}
@Test
public void testOnTransactionReady_taskFragmentParentInfoChanged() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final Configuration taskConfig = new Configuration();
transaction.addChange(new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(TASK_ID)
.setTaskConfiguration(taskConfig));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID),
eq(taskConfig));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
}
@Test
public void testOnTransactionReady_taskFragmentParentError() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final IBinder errorToken = new Binder();
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
final int opType = HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
final Exception exception = new SecurityException("test");
final Bundle errorBundle = TaskFragmentOrganizer.putErrorInfoInBundle(exception, info,
opType);
transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_ERROR)
.setErrorCallbackToken(errorToken)
.setErrorBundle(errorBundle));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentError(any(), eq(errorToken), eq(info), eq(opType),
eq(exception));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
}
@Test
public void testOnTransactionReady_activityReparentedToTask() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final Intent intent = mock(Intent.class);
final IBinder activityToken = new Binder();
transaction.addChange(new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(TASK_ID)
.setActivityIntent(intent)
.setActivityToken(activityToken));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent),
eq(activityToken));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
}
/** Creates a mock activity in the organizer process. */ /** Creates a mock activity in the organizer process. */
private Activity createMockActivity() { private Activity createMockActivity() {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);

View File

@@ -234,10 +234,8 @@ public class SplitPresenterTest {
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter).expandTaskFragment(eq(mTransaction), verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
eq(primaryTf.getTaskFragmentToken())); verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
verify(mPresenter).expandTaskFragment(eq(mTransaction),
eq(secondaryTf.getTaskFragmentToken()));
clearInvocations(mPresenter); clearInvocations(mPresenter);
@@ -245,10 +243,8 @@ public class SplitPresenterTest {
splitContainer, mActivity, null /* secondaryActivity */, splitContainer, mActivity, null /* secondaryActivity */,
new Intent(ApplicationProvider.getApplicationContext(), new Intent(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class))); MinimumDimensionActivity.class)));
verify(mPresenter).expandTaskFragment(eq(mTransaction), verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
eq(primaryTf.getTaskFragmentToken())); verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
verify(mPresenter).expandTaskFragment(eq(mTransaction),
eq(secondaryTf.getTaskFragmentToken()));
} }
private Activity createMockActivity() { private Activity createMockActivity() {

View File

@@ -18,10 +18,19 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 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.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_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_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 static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; 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 android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -47,14 +56,15 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -76,9 +86,12 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.List;
/** /**
* Build/Install/Run: * Build/Install/Run:
* atest WmTests:TaskFragmentOrganizerControllerTest * atest WmTests:TaskFragmentOrganizerControllerTest
@@ -107,6 +120,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
private TaskFragmentInfo mTaskFragmentInfo; private TaskFragmentInfo mTaskFragmentInfo;
@Mock @Mock
private Task mTask; private Task mTask;
@Captor
private ArgumentCaptor<TaskFragmentTransaction> mTransactionCaptor;
@Before @Before
public void setup() throws RemoteException { public void setup() throws RemoteException {
@@ -166,7 +181,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
// Send callback when the TaskFragment is attached. // Send callback when the TaskFragment is attached.
setupMockParent(mTaskFragment, mTask); setupMockParent(mTaskFragment, mTask);
@@ -174,7 +189,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentAppeared(any(), any()); assertTaskFragmentAppearedTransaction();
} }
@Test @Test
@@ -187,15 +202,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment); mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
// Call onTaskFragmentAppeared first. // Call onTaskFragmentAppeared first.
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentAppeared(any(), any()); verify(mOrganizer).onTransactionReady(any());
// No callback if the info is not changed. // No callback if the info is not changed.
clearInvocations(mOrganizer);
doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
@@ -203,7 +219,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment); mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
// Trigger callback if the info is changed. // Trigger callback if the info is changed.
doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -212,7 +228,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment); mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentInfoChanged(any(), eq(mTaskFragmentInfo)); assertTaskFragmentInfoChangedTransaction();
} }
@Test @Test
@@ -223,7 +239,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentVanished(any(), any()); assertTaskFragmentVanishedTransaction();
} }
@Test @Test
@@ -236,10 +252,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any()); assertTaskFragmentVanishedTransaction();
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
// Not trigger onTaskFragmentInfoChanged. // Not trigger onTaskFragmentInfoChanged.
// Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged. // Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
@@ -252,10 +265,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any()); assertTaskFragmentVanishedTransaction();
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
} }
@Test @Test
@@ -268,24 +278,30 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any()); assertTaskFragmentParentInfoChangedTransaction(mTask);
// No extra callback if the info is not changed. // No extra parent info changed callback if the info is not changed.
clearInvocations(mOrganizer); clearInvocations(mOrganizer);
mController.onTaskFragmentInfoChanged( mController.onTaskFragmentInfoChanged(
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any()); verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertEquals(1, changes.size());
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, change.getType());
// Trigger callback if the size is changed. // Trigger callback if the size is changed.
clearInvocations(mOrganizer);
mTask.getConfiguration().smallestScreenWidthDp = 100; mTask.getConfiguration().smallestScreenWidthDp = 100;
mController.onTaskFragmentInfoChanged( mController.onTaskFragmentInfoChanged(
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any()); assertTaskFragmentParentInfoChangedTransaction(mTask);
// Trigger callback if the windowing mode is changed. // Trigger callback if the windowing mode is changed.
clearInvocations(mOrganizer); clearInvocations(mOrganizer);
@@ -294,7 +310,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any()); assertTaskFragmentParentInfoChangedTransaction(mTask);
} }
@Test @Test
@@ -303,11 +319,12 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.registerOrganizer(mIOrganizer); mController.registerOrganizer(mIOrganizer);
mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(), mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
mErrorToken, null /* taskFragment */, -1 /* opType */, exception); mErrorToken, null /* taskFragment */, HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
exception);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), eq(null), eq(-1), assertTaskFragmentErrorTransaction(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
eq(exception)); exception.getClass());
} }
@Test @Test
@@ -326,17 +343,17 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onActivityReparentedToTask(activity); mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onActivityReparentedToTask(any(), anyInt(), any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
// Notify organizer if it was embedded before entered Pip. // Notify organizer if it was embedded before entered Pip.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
mController.onActivityReparentedToTask(activity); mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent), assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
eq(activity.token));
// Notify organizer if there is any embedded in the Task. // Notify organizer if there is any embedded in the Task.
clearInvocations(mOrganizer);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task) .setParentTask(task)
.setOrganizer(mOrganizer) .setOrganizer(mOrganizer)
@@ -348,9 +365,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onActivityReparentedToTask(activity); mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, times(2)) assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
.onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
eq(activity.token));
} }
@Test @Test
@@ -370,25 +385,31 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
activity.info.applicationInfo.uid = uid; activity.info.applicationInfo.uid = uid;
doReturn(pid + 1).when(activity).getPid(); doReturn(pid + 1).when(activity).getPid();
task.effectiveUid = uid; task.effectiveUid = uid;
final ArgumentCaptor<IBinder> token = ArgumentCaptor.forClass(IBinder.class);
// Notify organizer if it was embedded before entered Pip. // Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process. // Create a temporary token since the activity doesn't belong to the same process.
clearInvocations(mOrganizer);
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
mController.onActivityReparentedToTask(activity); mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
// Allow organizer to reparent activity in other process using the temporary token. // Allow organizer to reparent activity in other process using the temporary token.
verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent), verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
token.capture()); final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final IBinder temporaryToken = token.getValue(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertNotEquals(activity.token, temporaryToken); assertFalse(changes.isEmpty());
mTransaction.reparentActivityToTaskFragment(mFragmentToken, temporaryToken); final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
assertEquals(activity.intent, change.getActivityIntent());
assertNotEquals(activity.token, change.getActivityToken());
mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
mWindowOrganizerController.applyTransaction(mTransaction); mWindowOrganizerController.applyTransaction(mTransaction);
assertEquals(mTaskFragment, activity.getTaskFragment()); assertEquals(mTaskFragment, activity.getTaskFragment());
// The temporary token can only be used once. // The temporary token can only be used once.
assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer, temporaryToken)); assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer,
change.getActivityToken()));
} }
@Test @Test
@@ -901,7 +922,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
// Verifies that event was not sent // Verifies that event was not sent
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
} }
@Test @Test
@@ -927,7 +948,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
// Verifies that event was not sent // Verifies that event was not sent
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
// Mock the task becomes visible, and activity resumed // Mock the task becomes visible, and activity resumed
doReturn(true).when(task).shouldBeVisible(any()); doReturn(true).when(task).shouldBeVisible(any());
@@ -935,7 +956,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Verifies that event is sent. // Verifies that event is sent.
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer).onTransactionReady(any());
} }
/** /**
@@ -969,7 +990,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
clearInvocations(mOrganizer); clearInvocations(mOrganizer);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer).onTransactionReady(any());
} }
/** /**
@@ -996,21 +1017,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
taskFragment.mTaskFragmentAppearedSent = true; taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer).onTransactionReady(any());
// Verify the info changed callback is not called when the task is invisible // Verify the info changed callback is not called when the task is invisible
clearInvocations(mOrganizer); clearInvocations(mOrganizer);
doReturn(false).when(task).shouldBeVisible(any()); doReturn(false).when(task).shouldBeVisible(any());
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer, never()).onTransactionReady(any());
// Finish the embedded activity, and verify the info changed callback is called because the // Finish the embedded activity, and verify the info changed callback is called because the
// TaskFragment is becoming empty. // TaskFragment is becoming empty.
embeddedActivity.finishing = true; embeddedActivity.finishing = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents(); mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); verify(mOrganizer).onTransactionReady(any());
} }
/** /**
@@ -1120,9 +1141,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// The pending event will be dispatched on the handler (from requestTraversal). // The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler); waitHandlerIdle(mWm.mAnimationHandler);
verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(), assertTaskFragmentErrorTransaction(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT,
eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT), SecurityException.class);
any(SecurityException.class));
} }
@Test @Test
@@ -1159,8 +1179,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// The pending event will be dispatched on the handler (from requestTraversal). // The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler); waitHandlerIdle(mWm.mAnimationHandler);
verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(), assertTaskFragmentErrorTransaction(HIERARCHY_OP_TYPE_REPARENT_CHILDREN,
eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class)); SecurityException.class);
} }
@Test @Test
@@ -1264,6 +1284,87 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
} }
} }
/** Asserts that there will be a transaction for TaskFragment appeared. */
private void assertTaskFragmentAppearedTransaction() {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
// Appeared will come with parent info changed.
final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
}
/** Asserts that there will be a transaction for TaskFragment info changed. */
private void assertTaskFragmentInfoChangedTransaction() {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
// InfoChanged may come with parent info changed.
final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
}
/** Asserts that there will be a transaction for TaskFragment vanished. */
private void assertTaskFragmentVanishedTransaction() {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_TASK_FRAGMENT_VANISHED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
}
/** Asserts that there will be a transaction for TaskFragment vanished. */
private void assertTaskFragmentParentInfoChangedTransaction(@NonNull Task task) {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
assertEquals(task.getConfiguration(), change.getTaskConfiguration());
}
/** Asserts that there will be a transaction for TaskFragment error. */
private void assertTaskFragmentErrorTransaction(int opType, @NonNull Class<?> exceptionClass) {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_TASK_FRAGMENT_ERROR, change.getType());
assertEquals(mErrorToken, change.getErrorCallbackToken());
final Bundle errorBundle = change.getErrorBundle();
assertEquals(opType, errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE));
assertEquals(exceptionClass, errorBundle.getSerializable(
KEY_ERROR_CALLBACK_THROWABLE, Throwable.class).getClass());
}
/** Asserts that there will be a transaction for activity reparented to Task. */
private void assertActivityReparentedToTaskTransaction(int taskId, @NonNull Intent intent,
@NonNull IBinder activityToken) {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(taskId, change.getTaskId());
assertEquals(intent, change.getActivityIntent());
assertEquals(activityToken, change.getActivityToken());
}
/** Setups an embedded TaskFragment in a PIP Task. */ /** Setups an embedded TaskFragment in a PIP Task. */
private void setupTaskFragmentInPip() { private void setupTaskFragmentInPip() {
mOrganizer.applyTransaction(mTransaction); mOrganizer.applyTransaction(mTransaction);