Merge "Handle TaskFragmentOrganizer#onTransactionReady in sync" into tm-qpr-dev
This commit is contained in:
@@ -47,11 +47,24 @@ import java.util.concurrent.Executor;
|
||||
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";
|
||||
private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
|
||||
private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
|
||||
public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable";
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@@ -61,7 +74,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
|
||||
public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
|
||||
@Nullable TaskFragmentInfo info, int opType) {
|
||||
final Bundle errorBundle = new Bundle();
|
||||
errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
|
||||
errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception);
|
||||
if (info != null) {
|
||||
errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
|
||||
}
|
||||
@@ -342,6 +355,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
|
||||
* @hide
|
||||
*/
|
||||
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 List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
|
||||
for (TaskFragmentTransaction.Change change : changes) {
|
||||
@@ -391,7 +407,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
|
||||
errorBundle.getParcelable(
|
||||
KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
|
||||
errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
|
||||
errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
|
||||
errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE,
|
||||
java.lang.Throwable.class));
|
||||
break;
|
||||
case TYPE_ACTIVITY_REPARENTED_TO_TASK:
|
||||
|
||||
@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
import android.app.Activity;
|
||||
import android.app.WindowConfiguration.WindowingMode;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
@@ -29,6 +28,7 @@ import android.util.ArrayMap;
|
||||
import android.window.TaskFragmentCreationParams;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.TaskFragmentOrganizer;
|
||||
import android.window.TaskFragmentTransaction;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -62,18 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
* Callback that notifies the controller about changes to task fragments.
|
||||
*/
|
||||
interface TaskFragmentCallback {
|
||||
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
|
||||
@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);
|
||||
void onTransactionReady(@NonNull TaskFragmentTransaction transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,50 +259,16 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull TaskFragmentInfo taskFragmentInfo) {
|
||||
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
|
||||
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
|
||||
mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
|
||||
void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
|
||||
mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
|
||||
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
|
||||
mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
|
||||
int taskId, @NonNull Configuration parentConfig) {
|
||||
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);
|
||||
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
|
||||
mCallback.onTransactionReady(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ package androidx.window.extensions.embedding;
|
||||
import static android.app.ActivityManager.START_SUCCESS;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
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_START_ACTIVITY_IN_TASK_FRAGMENT;
|
||||
|
||||
@@ -53,6 +62,7 @@ import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.util.SparseArray;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.TaskFragmentTransaction;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
@@ -144,203 +154,312 @@ 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
|
||||
public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull TaskFragmentInfo taskFragmentInfo) {
|
||||
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
|
||||
synchronized (mLock) {
|
||||
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
if (container == null) {
|
||||
return;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
container.setInfo(wct, taskFragmentInfo);
|
||||
if (container.isFinished()) {
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
} else {
|
||||
// Update with the latest Task configuration.
|
||||
updateContainer(wct, container);
|
||||
}
|
||||
// Notify the server, and the server should apply the WindowContainerTransaction.
|
||||
mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct);
|
||||
updateCallbackIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
|
||||
/**
|
||||
* 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) {
|
||||
synchronized (mLock) {
|
||||
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean wasInPip = isInPictureInPicture(container);
|
||||
container.setInfo(wct, taskFragmentInfo);
|
||||
final boolean isInPip = isInPictureInPicture(container);
|
||||
// Check if there are no running activities - consider the container empty if there are
|
||||
// no non-finishing activities left.
|
||||
if (!taskFragmentInfo.hasRunningActivity()) {
|
||||
if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
|
||||
// Do not finish the dependents if the last activity is reparented to PiP.
|
||||
// Instead, the original split should be cleanup, and the dependent may be
|
||||
// expanded to fullscreen.
|
||||
cleanupForEnterPip(wct, container);
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
|
||||
// Do not finish the dependents if this TaskFragment was cleared due to
|
||||
// launching activity in the Task.
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
} else if (!container.isWaitingActivityAppear()) {
|
||||
// Do not finish the container before the expected activity appear until
|
||||
// timeout.
|
||||
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
|
||||
}
|
||||
} else if (wasInPip && isInPip) {
|
||||
// No update until exit PIP.
|
||||
return;
|
||||
} else if (isInPip) {
|
||||
// Enter PIP.
|
||||
// All overrides will be cleanup.
|
||||
container.setLastRequestedBounds(null /* bounds */);
|
||||
container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
|
||||
container.setInfo(wct, taskFragmentInfo);
|
||||
if (container.isFinished()) {
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
} else {
|
||||
// Update with the latest Task configuration.
|
||||
updateContainer(wct, container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean wasInPip = isInPictureInPicture(container);
|
||||
container.setInfo(wct, taskFragmentInfo);
|
||||
final boolean isInPip = isInPictureInPicture(container);
|
||||
// Check if there are no running activities - consider the container empty if there are
|
||||
// no non-finishing activities left.
|
||||
if (!taskFragmentInfo.hasRunningActivity()) {
|
||||
if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
|
||||
// Do not finish the dependents if the last activity is reparented to PiP.
|
||||
// Instead, the original split should be cleanup, and the dependent may be
|
||||
// expanded to fullscreen.
|
||||
cleanupForEnterPip(wct, container);
|
||||
} else if (wasInPip) {
|
||||
// Exit PIP.
|
||||
// Updates the presentation of the container. Expand or launch placeholder if
|
||||
// needed.
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
|
||||
// Do not finish the dependents if this TaskFragment was cleared due to
|
||||
// launching activity in the Task.
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
} else if (!container.isWaitingActivityAppear()) {
|
||||
// Do not finish the container before the expected activity appear until
|
||||
// timeout.
|
||||
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
|
||||
}
|
||||
} else if (wasInPip && isInPip) {
|
||||
// No update until exit PIP.
|
||||
return;
|
||||
} else if (isInPip) {
|
||||
// Enter PIP.
|
||||
// All overrides will be cleanup.
|
||||
container.setLastRequestedBounds(null /* bounds */);
|
||||
container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
|
||||
cleanupForEnterPip(wct, container);
|
||||
} else if (wasInPip) {
|
||||
// Exit PIP.
|
||||
// Updates the presentation of the container. Expand or launch placeholder if
|
||||
// needed.
|
||||
updateContainer(wct, container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
if (container != null) {
|
||||
// Cleanup if the TaskFragment vanished is not requested by the organizer.
|
||||
removeContainer(container);
|
||||
// Make sure the top container is updated.
|
||||
final TaskFragmentContainer newTopContainer = getTopActiveContainer(
|
||||
container.getTaskId());
|
||||
if (newTopContainer != null) {
|
||||
updateContainer(wct, newTopContainer);
|
||||
}
|
||||
}
|
||||
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
onTaskConfigurationChanged(taskId, parentConfig);
|
||||
if (isInPictureInPicture(parentConfig)) {
|
||||
// No need to update presentation in PIP until the Task exit PIP.
|
||||
return;
|
||||
}
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.isEmpty()) {
|
||||
Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
|
||||
return;
|
||||
}
|
||||
// Update all TaskFragments in the Task. Make a copy of the list since some may be
|
||||
// removed on updating.
|
||||
final List<TaskFragmentContainer> containers =
|
||||
new ArrayList<>(taskContainer.mContainers);
|
||||
for (int i = containers.size() - 1; i >= 0; i--) {
|
||||
final TaskFragmentContainer container = containers.get(i);
|
||||
// Wait until onTaskFragmentAppeared to update new container.
|
||||
if (!container.isFinished() && !container.isWaitingActivityAppear()) {
|
||||
updateContainer(wct, container);
|
||||
}
|
||||
updateCallbackIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull TaskFragmentInfo taskFragmentInfo) {
|
||||
synchronized (mLock) {
|
||||
final TaskFragmentContainer container = getContainer(
|
||||
taskFragmentInfo.getFragmentToken());
|
||||
if (container != null) {
|
||||
// Cleanup if the TaskFragment vanished is not requested by the organizer.
|
||||
removeContainer(container);
|
||||
// Make sure the top container is updated.
|
||||
final TaskFragmentContainer newTopContainer = getTopActiveContainer(
|
||||
container.getTaskId());
|
||||
if (newTopContainer != null) {
|
||||
updateContainer(wct, newTopContainer);
|
||||
}
|
||||
updateCallbackIfNecessary();
|
||||
}
|
||||
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
|
||||
int taskId, @NonNull Configuration parentConfig) {
|
||||
synchronized (mLock) {
|
||||
onTaskConfigurationChanged(taskId, parentConfig);
|
||||
if (isInPictureInPicture(parentConfig)) {
|
||||
// No need to update presentation in PIP until the Task exit PIP.
|
||||
return;
|
||||
}
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.isEmpty()) {
|
||||
Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
|
||||
return;
|
||||
}
|
||||
// Update all TaskFragments in the Task. Make a copy of the list since some may be
|
||||
// removed on updating.
|
||||
final List<TaskFragmentContainer> containers =
|
||||
new ArrayList<>(taskContainer.mContainers);
|
||||
for (int i = containers.size() - 1; i >= 0; i--) {
|
||||
final TaskFragmentContainer container = containers.get(i);
|
||||
// Wait until onTaskFragmentAppeared to update new container.
|
||||
if (!container.isFinished() && !container.isWaitingActivityAppear()) {
|
||||
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,
|
||||
@NonNull IBinder activityToken) {
|
||||
synchronized (mLock) {
|
||||
// If the activity belongs to the current app process, we treat it as a new activity
|
||||
// launch.
|
||||
final Activity activity = getActivity(activityToken);
|
||||
if (activity != null) {
|
||||
// We don't allow split as primary for new launch because we currently only support
|
||||
// launching to top. We allow split as primary for activity reparent because the
|
||||
// activity may be split as primary before it is reparented out. In that case, we
|
||||
// want to show it as primary again when it is reparented back.
|
||||
if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
|
||||
// When there is no embedding rule matched, try to place it in the top container
|
||||
// like a normal launch.
|
||||
placeActivityInTopContainer(wct, activity);
|
||||
}
|
||||
updateCallbackIfNecessary();
|
||||
return;
|
||||
}
|
||||
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.isInPictureInPicture()) {
|
||||
// We don't embed activity when it is in PIP.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the activity belongs to a different app process, we treat it as starting new
|
||||
// intent, since both actions might result in a new activity that should appear in an
|
||||
// organized TaskFragment.
|
||||
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
|
||||
activityIntent, null /* launchingActivity */);
|
||||
if (targetContainer == null) {
|
||||
// If the activity belongs to the current app process, we treat it as a new activity
|
||||
// launch.
|
||||
final Activity activity = getActivity(activityToken);
|
||||
if (activity != null) {
|
||||
// We don't allow split as primary for new launch because we currently only support
|
||||
// launching to top. We allow split as primary for activity reparent because the
|
||||
// activity may be split as primary before it is reparented out. In that case, we
|
||||
// want to show it as primary again when it is reparented back.
|
||||
if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
|
||||
// When there is no embedding rule matched, try to place it in the top container
|
||||
// like a normal launch.
|
||||
targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
placeActivityInTopContainer(wct, activity);
|
||||
}
|
||||
if (targetContainer == null) {
|
||||
return;
|
||||
}
|
||||
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
|
||||
activityToken);
|
||||
// Because the activity does not belong to the organizer process, we wait until
|
||||
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
|
||||
return;
|
||||
}
|
||||
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.isInPictureInPicture()) {
|
||||
// We don't embed activity when it is in PIP.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the activity belongs to a different app process, we treat it as starting new
|
||||
// intent, since both actions might result in a new activity that should appear in an
|
||||
// organized TaskFragment.
|
||||
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
|
||||
activityIntent, null /* launchingActivity */);
|
||||
if (targetContainer == null) {
|
||||
// When there is no embedding rule matched, try to place it in the top container
|
||||
// like a normal launch.
|
||||
targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
}
|
||||
if (targetContainer == null) {
|
||||
return;
|
||||
}
|
||||
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
|
||||
activityToken);
|
||||
// Because the activity does not belong to the organizer process, we wait until
|
||||
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
|
||||
@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
|
||||
synchronized (mLock) {
|
||||
switch (opType) {
|
||||
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
|
||||
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
|
||||
final TaskFragmentContainer container;
|
||||
if (taskFragmentInfo != null) {
|
||||
container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
} else {
|
||||
container = null;
|
||||
}
|
||||
if (container == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the latest taskFragmentInfo and perform necessary clean-up
|
||||
container.setInfo(wct, taskFragmentInfo);
|
||||
container.clearPendingAppearedActivities();
|
||||
if (container.isEmpty()) {
|
||||
mPresenter.cleanupContainer(wct, container,
|
||||
false /* shouldFinishDependent */);
|
||||
}
|
||||
/**
|
||||
* Called when the {@link WindowContainerTransaction} created with
|
||||
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
|
||||
*
|
||||
* @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) {
|
||||
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
|
||||
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
|
||||
final TaskFragmentContainer container;
|
||||
if (taskFragmentInfo != null) {
|
||||
container = getContainer(taskFragmentInfo.getFragmentToken());
|
||||
} else {
|
||||
container = null;
|
||||
}
|
||||
if (container == null) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo
|
||||
+ ", opType = " + opType);
|
||||
|
||||
// Update the latest taskFragmentInfo and perform necessary clean-up
|
||||
container.setInfo(wct, taskFragmentInfo);
|
||||
container.clearPendingAppearedActivities();
|
||||
if (container.isEmpty()) {
|
||||
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo
|
||||
+ ", opType = " + opType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.graphics.Point;
|
||||
import android.os.Handler;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.TaskFragmentTransaction;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
@@ -129,6 +130,14 @@ public class JetpackTaskFragmentOrganizerTest {
|
||||
WINDOWING_MODE_UNDEFINED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTransactionReady() {
|
||||
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
|
||||
mOrganizer.onTransactionReady(transaction);
|
||||
|
||||
verify(mCallback).onTransactionReady(transaction);
|
||||
}
|
||||
|
||||
private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) {
|
||||
return new TaskFragmentInfo(container.getTaskFragmentToken(),
|
||||
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
|
||||
|
||||
@@ -19,6 +19,13 @@ package androidx.window.extensions.embedding;
|
||||
import static android.app.ActivityManager.START_CANCELED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
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.TASK_BOUNDS;
|
||||
@@ -67,6 +74,8 @@ import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.TaskFragmentOrganizer;
|
||||
import android.window.TaskFragmentTransaction;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -980,6 +989,98 @@ public class SplitControllerTest {
|
||||
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. */
|
||||
private Activity createMockActivity() {
|
||||
final Activity activity = mock(Activity.class);
|
||||
|
||||
@@ -234,10 +234,8 @@ public class SplitPresenterTest {
|
||||
|
||||
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
|
||||
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
|
||||
verify(mPresenter).expandTaskFragment(eq(mTransaction),
|
||||
eq(primaryTf.getTaskFragmentToken()));
|
||||
verify(mPresenter).expandTaskFragment(eq(mTransaction),
|
||||
eq(secondaryTf.getTaskFragmentToken()));
|
||||
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
|
||||
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
|
||||
|
||||
clearInvocations(mPresenter);
|
||||
|
||||
@@ -245,10 +243,8 @@ public class SplitPresenterTest {
|
||||
splitContainer, mActivity, null /* secondaryActivity */,
|
||||
new Intent(ApplicationProvider.getApplicationContext(),
|
||||
MinimumDimensionActivity.class)));
|
||||
verify(mPresenter).expandTaskFragment(eq(mTransaction),
|
||||
eq(primaryTf.getTaskFragmentToken()));
|
||||
verify(mPresenter).expandTaskFragment(eq(mTransaction),
|
||||
eq(secondaryTf.getTaskFragmentToken()));
|
||||
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
|
||||
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
|
||||
}
|
||||
|
||||
private Activity createMockActivity() {
|
||||
|
||||
@@ -18,10 +18,19 @@ package com.android.server.wm;
|
||||
|
||||
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.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 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_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.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
@@ -76,9 +86,12 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Build/Install/Run:
|
||||
* atest WmTests:TaskFragmentOrganizerControllerTest
|
||||
@@ -107,6 +120,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
private TaskFragmentInfo mTaskFragmentInfo;
|
||||
@Mock
|
||||
private Task mTask;
|
||||
@Captor
|
||||
private ArgumentCaptor<TaskFragmentTransaction> mTransactionCaptor;
|
||||
|
||||
@Before
|
||||
public void setup() throws RemoteException {
|
||||
@@ -166,7 +181,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Send callback when the TaskFragment is attached.
|
||||
setupMockParent(mTaskFragment, mTask);
|
||||
@@ -174,7 +189,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentAppeared(any(), any());
|
||||
assertTaskFragmentAppearedTransaction();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -187,15 +202,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Call onTaskFragmentAppeared first.
|
||||
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentAppeared(any(), any());
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
|
||||
// No callback if the info is not changed.
|
||||
clearInvocations(mOrganizer);
|
||||
doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
|
||||
doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
|
||||
|
||||
@@ -203,7 +219,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Trigger callback if the info is changed.
|
||||
doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
|
||||
@@ -212,7 +228,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentInfoChanged(any(), eq(mTaskFragmentInfo));
|
||||
assertTaskFragmentInfoChangedTransaction();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -223,7 +239,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentVanished(any(), any());
|
||||
assertTaskFragmentVanishedTransaction();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -236,10 +252,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
|
||||
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
|
||||
verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
|
||||
assertTaskFragmentVanishedTransaction();
|
||||
|
||||
// Not trigger onTaskFragmentInfoChanged.
|
||||
// Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
|
||||
@@ -252,10 +265,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
|
||||
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
|
||||
verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
|
||||
assertTaskFragmentVanishedTransaction();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -268,24 +278,30 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
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);
|
||||
|
||||
mController.onTaskFragmentInfoChanged(
|
||||
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
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.
|
||||
clearInvocations(mOrganizer);
|
||||
mTask.getConfiguration().smallestScreenWidthDp = 100;
|
||||
mController.onTaskFragmentInfoChanged(
|
||||
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
|
||||
assertTaskFragmentParentInfoChangedTransaction(mTask);
|
||||
|
||||
// Trigger callback if the windowing mode is changed.
|
||||
clearInvocations(mOrganizer);
|
||||
@@ -294,7 +310,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
|
||||
assertTaskFragmentParentInfoChangedTransaction(mTask);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -303,11 +319,12 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
|
||||
mController.registerOrganizer(mIOrganizer);
|
||||
mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
|
||||
mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
|
||||
mErrorToken, null /* taskFragment */, HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
|
||||
exception);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), eq(null), eq(-1),
|
||||
eq(exception));
|
||||
assertTaskFragmentErrorTransaction(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
|
||||
exception.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -326,17 +343,17 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onActivityReparentedToTask(activity);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, never()).onActivityReparentedToTask(any(), anyInt(), any(), any());
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Notify organizer if it was embedded before entered Pip.
|
||||
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
|
||||
mController.onActivityReparentedToTask(activity);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
|
||||
eq(activity.token));
|
||||
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
|
||||
|
||||
// Notify organizer if there is any embedded in the Task.
|
||||
clearInvocations(mOrganizer);
|
||||
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
||||
.setParentTask(task)
|
||||
.setOrganizer(mOrganizer)
|
||||
@@ -348,9 +365,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.onActivityReparentedToTask(activity);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
verify(mOrganizer, times(2))
|
||||
.onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
|
||||
eq(activity.token));
|
||||
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -370,25 +385,31 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
activity.info.applicationInfo.uid = uid;
|
||||
doReturn(pid + 1).when(activity).getPid();
|
||||
task.effectiveUid = uid;
|
||||
final ArgumentCaptor<IBinder> token = ArgumentCaptor.forClass(IBinder.class);
|
||||
|
||||
// Notify organizer if it was embedded before entered Pip.
|
||||
// Create a temporary token since the activity doesn't belong to the same process.
|
||||
clearInvocations(mOrganizer);
|
||||
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
|
||||
mController.onActivityReparentedToTask(activity);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
// Allow organizer to reparent activity in other process using the temporary token.
|
||||
verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
|
||||
token.capture());
|
||||
final IBinder temporaryToken = token.getValue();
|
||||
assertNotEquals(activity.token, temporaryToken);
|
||||
mTransaction.reparentActivityToTaskFragment(mFragmentToken, temporaryToken);
|
||||
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(task.mTaskId, change.getTaskId());
|
||||
assertEquals(activity.intent, change.getActivityIntent());
|
||||
assertNotEquals(activity.token, change.getActivityToken());
|
||||
mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
|
||||
mWindowOrganizerController.applyTransaction(mTransaction);
|
||||
|
||||
assertEquals(mTaskFragment, activity.getTaskFragment());
|
||||
// The temporary token can only be used once.
|
||||
assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer, temporaryToken));
|
||||
assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer,
|
||||
change.getActivityToken()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -901,7 +922,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
// Verifies that event was not sent
|
||||
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -927,7 +948,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
// 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
|
||||
doReturn(true).when(task).shouldBeVisible(any());
|
||||
@@ -935,7 +956,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
|
||||
// Verifies that event is sent.
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -969,7 +990,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
clearInvocations(mOrganizer);
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -996,21 +1017,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
taskFragment.mTaskFragmentAppearedSent = true;
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
|
||||
// Verify the info changed callback is not called when the task is invisible
|
||||
clearInvocations(mOrganizer);
|
||||
doReturn(false).when(task).shouldBeVisible(any());
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
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
|
||||
// TaskFragment is becoming empty.
|
||||
embeddedActivity.finishing = true;
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
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).
|
||||
waitHandlerIdle(mWm.mAnimationHandler);
|
||||
|
||||
verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
|
||||
eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
|
||||
any(SecurityException.class));
|
||||
assertTaskFragmentErrorTransaction(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT,
|
||||
SecurityException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1159,8 +1179,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
// The pending event will be dispatched on the handler (from requestTraversal).
|
||||
waitHandlerIdle(mWm.mAnimationHandler);
|
||||
|
||||
verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
|
||||
eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class));
|
||||
assertTaskFragmentErrorTransaction(HIERARCHY_OP_TYPE_REPARENT_CHILDREN,
|
||||
SecurityException.class);
|
||||
}
|
||||
|
||||
@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. */
|
||||
private void setupTaskFragmentInPip() {
|
||||
mOrganizer.applyTransaction(mTransaction);
|
||||
|
||||
Reference in New Issue
Block a user