blob: 60be9d16d749a50f2a390c7d633addf7c78cae57 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.util.Size;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
* on the server side.
*/
// Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
// SplitController.mTaskContainers which is guarded.
@SuppressWarnings("GuardedBy")
class TaskFragmentContainer {
private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
@NonNull
private final SplitController mController;
/**
* Client-created token that uniquely identifies the task fragment container instance.
*/
@NonNull
private final IBinder mToken;
/** Parent leaf Task. */
@NonNull
private final TaskContainer mTaskContainer;
/**
* Server-provided task fragment information.
*/
@VisibleForTesting
TaskFragmentInfo mInfo;
/**
* Activity tokens that are being reparented or being started to this container, but haven't
* been added to {@link #mInfo} yet.
*/
@VisibleForTesting
final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();
/**
* When this container is created for an {@link Intent} to start within, we store that Intent
* until the container becomes non-empty on the server side, so that we can use it to check
* rules associated with this container.
*/
@Nullable
private Intent mPendingAppearedIntent;
/**
* The activities that were explicitly requested to be launched in its current TaskFragment,
* but haven't been added to {@link #mInfo} yet.
*/
final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>();
/** Containers that are dependent on this one and should be completely destroyed on exit. */
private final List<TaskFragmentContainer> mContainersToFinishOnExit =
new ArrayList<>();
/**
* Individual associated activity tokens in different containers that should be finished on
* exit.
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
/**
* Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
*/
private final Rect mLastRequestedBounds = new Rect();
/**
* Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
*/
@WindowingMode
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
/**
* TaskFragmentAnimationParams that was requested last via
* {@link android.window.WindowContainerTransaction}.
*/
@NonNull
private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
/**
* TaskFragment token that was requested last via
* {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
*/
@Nullable
private IBinder mLastAdjacentTaskFragment;
/**
* {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last
* via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
*/
@Nullable
private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams;
/**
* TaskFragment token that was requested last via
* {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}.
*/
@Nullable
private IBinder mLastCompanionTaskFragment;
/**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@VisibleForTesting
@Nullable
Runnable mAppearEmptyTimeout;
/**
* Whether this TaskFragment contains activities of another process/package.
*/
private boolean mHasCrossProcessActivities;
/**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
"One and only one of pending activity and intent must be non-null");
}
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
throw new IllegalArgumentException(
"pairedPrimaryContainer must be in the same Task");
}
final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
taskContainer.mContainers.add(primaryIndex + 1, this);
} else if (pendingAppearedActivity != null) {
// The TaskFragment will be positioned right above the pending appeared Activity. If any
// existing TaskFragment is empty with pending Intent, it is likely that the Activity of
// the pending Intent hasn't been created yet, so the new Activity should be below the
// empty TaskFragment.
int i = taskContainer.mContainers.size() - 1;
for (; i >= 0; i--) {
final TaskFragmentContainer container = taskContainer.mContainers.get(i);
if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
break;
}
}
taskContainer.mContainers.add(i + 1, this);
} else {
taskContainer.mContainers.add(this);
}
if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity);
}
mPendingAppearedIntent = pendingAppearedIntent;
}
/**
* Returns the client-created token that uniquely identifies this container.
*/
@NonNull
IBinder getTaskFragmentToken() {
return mToken;
}
/** List of non-finishing activities that belong to this container and live in this process. */
@NonNull
List<Activity> collectNonFinishingActivities() {
final List<Activity> allActivities = new ArrayList<>();
if (mInfo != null) {
// Add activities reported from the server.
for (IBinder token : mInfo.getActivities()) {
final Activity activity = mController.getActivity(token);
if (activity != null && !activity.isFinishing()) {
allActivities.add(activity);
}
}
}
// Add the re-parenting activity, in case the server has not yet reported the task
// fragment info update with it placed in this container. We still want to apply rules
// in this intermediate state.
// Place those on top of the list since they will be on the top after reported from the
// server.
for (IBinder token : mPendingAppearedActivities) {
final Activity activity = mController.getActivity(token);
if (activity != null && !activity.isFinishing()) {
allActivities.add(activity);
}
}
return allActivities;
}
/** Whether this TaskFragment is visible. */
boolean isVisible() {
return mInfo != null && mInfo.isVisible();
}
/** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
boolean isInIntermediateState() {
if (mInfo == null) {
// Haven't received onTaskFragmentAppeared event.
return true;
}
if (mInfo.isEmpty()) {
// Empty TaskFragment will be removed or will have activity launched into it soon.
return true;
}
if (!mPendingAppearedActivities.isEmpty()) {
// Reparented activity hasn't appeared.
return true;
}
// Check if there is any reported activity that is no longer alive.
for (IBinder token : mInfo.getActivities()) {
final Activity activity = mController.getActivity(token);
if (activity == null && !mTaskContainer.isVisible()) {
// Activity can be null if the activity is not attached to process yet. That can
// happen when the activity is started in background.
continue;
}
if (activity == null || activity.isFinishing()) {
// One of the reported activity is no longer alive, wait for the server update.
return true;
}
}
return false;
}
@NonNull
ActivityStack toActivityStack() {
return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
}
/** Adds the activity that will be reparented to this container. */
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
final IBinder activityToken = pendingAppearedActivity.getActivityToken();
if (hasActivity(activityToken)) {
return;
}
// Remove the pending activity from other TaskFragments in case the activity is reparented
// again before the server update.
mTaskContainer.cleanupPendingAppearedActivity(activityToken);
mPendingAppearedActivities.add(activityToken);
updateActivityClientRecordTaskFragmentToken(activityToken);
}
/**
* Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
* activity. This makes sure the token is up-to-date if the activity is relaunched later.
*/
private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
final ActivityThread.ActivityClientRecord record = ActivityThread
.currentActivityThread().getActivityClient(activityToken);
if (record != null) {
record.mTaskFragmentToken = mToken;
}
}
void removePendingAppearedActivity(@NonNull IBinder activityToken) {
mPendingAppearedActivities.remove(activityToken);
// Also remove the activity from the mPendingInRequestedTaskFragmentActivities.
mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken);
}
@GuardedBy("mController.mLock")
void clearPendingAppearedActivities() {
final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
// Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
// current TaskFragment.
mPendingAppearedActivities.clear();
mPendingAppearedIntent = null;
// For removed pending activities, we need to update the them to their previous containers.
for (IBinder activityToken : cleanupActivities) {
final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
activityToken);
if (curContainer != null) {
curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
}
}
}
/** Called when the activity is destroyed. */
void onActivityDestroyed(@NonNull IBinder activityToken) {
removePendingAppearedActivity(activityToken);
if (mInfo != null) {
// Remove the activity now because there can be a delay before the server callback.
mInfo.getActivities().remove(activityToken);
}
mActivitiesToFinishOnExit.remove(activityToken);
}
@Nullable
Intent getPendingAppearedIntent() {
return mPendingAppearedIntent;
}
void setPendingAppearedIntent(@Nullable Intent intent) {
mPendingAppearedIntent = intent;
}
/**
* Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the
* pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has
* running activities).
*/
void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) {
if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) {
return;
}
mPendingAppearedIntent = null;
}
boolean hasActivity(@NonNull IBinder activityToken) {
// Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
// sure the controller considers this container as the one containing the activity.
// This is needed when the activity is added as pending appeared activity to one
// TaskFragment while it is also an appeared activity in another.
return mController.getContainerWithActivity(activityToken) == this;
}
/** Whether this activity has appeared in the TaskFragment on the server side. */
boolean hasAppearedActivity(@NonNull IBinder activityToken) {
return mInfo != null && mInfo.getActivities().contains(activityToken);
}
/**
* Whether we are waiting for this activity to appear in the TaskFragment on the server side.
*/
boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
return mPendingAppearedActivities.contains(activityToken);
}
int getRunningActivityCount() {
int count = mPendingAppearedActivities.size();
if (mInfo != null) {
count += mInfo.getRunningActivityCount();
}
return count;
}
/** Whether we are waiting for the TaskFragment to appear and become non-empty. */
boolean isWaitingActivityAppear() {
return !mIsFinished && (mInfo == null || mAppearEmptyTimeout != null);
}
@Nullable
TaskFragmentInfo getInfo() {
return mInfo;
}
@GuardedBy("mController.mLock")
void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
// pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
// it is still empty after timeout.
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
mAppearEmptyTimeout = () -> {
synchronized (mController.mLock) {
mAppearEmptyTimeout = null;
// Call without the pass-in wct when timeout. We need to applyWct directly
// in this case.
mController.onTaskFragmentAppearEmptyTimeout(this);
}
};
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
} else {
mAppearEmptyTimeout = null;
mController.onTaskFragmentAppearEmptyTimeout(wct, this);
}
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
mAppearEmptyTimeout = null;
}
mHasCrossProcessActivities = false;
mInfo = info;
if (mInfo == null || mInfo.isEmpty()) {
return;
}
// Contains activities of another process if the activities size is not matched to the
// running activity count
if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
mHasCrossProcessActivities = true;
}
// Only track the pending Intent when the container is empty.
mPendingAppearedIntent = null;
if (mPendingAppearedActivities.isEmpty()) {
return;
}
// Cleanup activities that were being re-parented
List<IBinder> infoActivities = mInfo.getActivities();
for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
final IBinder activityToken = mPendingAppearedActivities.get(i);
if (infoActivities.contains(activityToken)) {
removePendingAppearedActivity(activityToken);
}
}
}
@Nullable
Activity getTopNonFinishingActivity() {
final List<Activity> activities = collectNonFinishingActivities();
return activities.isEmpty() ? null : activities.get(activities.size() - 1);
}
@Nullable
Activity getBottomMostActivity() {
final List<Activity> activities = collectNonFinishingActivities();
return activities.isEmpty() ? null : activities.get(0);
}
boolean isEmpty() {
return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty());
}
/**
* Adds a container that should be finished when this container is finished.
*/
void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) {
if (mIsFinished) {
return;
}
mContainersToFinishOnExit.add(containerToFinish);
}
/**
* Removes a container that should be finished when this container is finished.
*/
void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
removeContainersToFinishOnExit(Collections.singletonList(containerToRemove));
}
/**
* Removes container list that should be finished when this container is finished.
*/
void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) {
if (mIsFinished) {
return;
}
mContainersToFinishOnExit.removeAll(containersToRemove);
}
/**
* Adds an activity that should be finished when this container is finished.
*/
void addActivityToFinishOnExit(@NonNull Activity activityToFinish) {
if (mIsFinished) {
return;
}
mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
}
/**
* Removes an activity that should be finished when this container is finished.
*/
void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) {
if (mIsFinished) {
return;
}
mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
}
/** Removes all dependencies that should be finished when this container is finished. */
void resetDependencies() {
if (mIsFinished) {
return;
}
mContainersToFinishOnExit.clear();
mActivitiesToFinishOnExit.clear();
}
/**
* Removes all activities that belong to this process and finishes other containers/activities
* configured to finish together.
*/
@GuardedBy("mController.mLock")
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */);
}
/**
* Removes all activities that belong to this process and finishes other containers/activities
* configured to finish together.
*/
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller,
boolean shouldRemoveRecord) {
if (!mIsFinished) {
mIsFinished = true;
if (mAppearEmptyTimeout != null) {
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
mAppearEmptyTimeout = null;
}
finishActivities(shouldFinishDependent, presenter, wct, controller);
}
if (mInfo == null) {
// Defer removal the container and wait until TaskFragment appeared.
return;
}
// Cleanup the visuals
presenter.deleteTaskFragment(wct, getTaskFragmentToken());
if (shouldRemoveRecord) {
// Cleanup the records
controller.removeContainer(this);
}
// Clean up task fragment information
mInfo = null;
}
@GuardedBy("mController.mLock")
private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
// Finish own activities
for (Activity activity : collectNonFinishingActivities()) {
if (!activity.isFinishing()
// In case we have requested to reparent the activity to another container (as
// pendingAppeared), we don't want to finish it with this container.
&& mController.getContainerWithActivity(activity) == this) {
wct.finishActivity(activity.getActivityToken());
}
}
if (!shouldFinishDependent) {
// Always finish the placeholder when the primary is finished.
finishPlaceholderIfAny(wct, presenter);
return;
}
// Finish dependent containers
for (TaskFragmentContainer container : mContainersToFinishOnExit) {
if (container.mIsFinished
|| controller.shouldRetainAssociatedContainer(this, container)) {
continue;
}
container.finish(true /* shouldFinishDependent */, presenter,
wct, controller);
}
mContainersToFinishOnExit.clear();
// Finish associated activities
for (IBinder activityToken : mActivitiesToFinishOnExit) {
final Activity activity = mController.getActivity(activityToken);
if (activity == null || activity.isFinishing()
|| controller.shouldRetainAssociatedActivity(this, activity)) {
continue;
}
wct.finishActivity(activity.getActivityToken());
}
mActivitiesToFinishOnExit.clear();
}
@GuardedBy("mController.mLock")
private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
@NonNull SplitPresenter presenter) {
final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
for (TaskFragmentContainer container : mContainersToFinishOnExit) {
if (container.mIsFinished) {
continue;
}
final SplitContainer splitContainer = mController.getActiveSplitForContainers(
this, container);
if (splitContainer != null && splitContainer.isPlaceholderContainer()
&& splitContainer.getSecondaryContainer() == container) {
// Remove the placeholder secondary TaskFragment.
containersToRemove.add(container);
}
}
mContainersToFinishOnExit.removeAll(containersToRemove);
for (TaskFragmentContainer container : containersToRemove) {
container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
}
}
boolean isFinished() {
return mIsFinished;
}
/**
* Checks if last requested bounds are equal to the provided value.
* The requested bounds are relative bounds in parent coordinate.
* @see WindowContainerTransaction#setRelativeBounds
*/
boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
return (relBounds == null && mLastRequestedBounds.isEmpty())
|| mLastRequestedBounds.equals(relBounds);
}
/**
* Updates the last requested bounds.
* The requested bounds are relative bounds in parent coordinate.
* @see WindowContainerTransaction#setRelativeBounds
*/
void setLastRequestedBounds(@Nullable Rect relBounds) {
if (relBounds == null) {
mLastRequestedBounds.setEmpty();
} else {
mLastRequestedBounds.set(relBounds);
}
}
/**
* Checks if last requested windowing mode is equal to the provided value.
* @see WindowContainerTransaction#setWindowingMode
*/
boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) {
return mLastRequestedWindowingMode == windowingMode;
}
/**
* Updates the last requested windowing mode.
* @see WindowContainerTransaction#setWindowingMode
*/
void setLastRequestedWindowingMode(@WindowingMode int windowingModes) {
mLastRequestedWindowingMode = windowingModes;
}
/**
* Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
* @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
boolean areLastRequestedAnimationParamsEqual(
@NonNull TaskFragmentAnimationParams animationParams) {
return mLastAnimationParams.equals(animationParams);
}
/**
* Updates the last requested {@link TaskFragmentAnimationParams}.
* @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
mLastAnimationParams = animationParams;
}
/**
* Checks if last requested adjacent TaskFragment token and params are equal to the provided
* values.
* @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
* @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
*/
boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken,
@Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) {
return Objects.equals(mLastAdjacentTaskFragment, fragmentToken)
&& Objects.equals(mLastAdjacentParams, params);
}
/**
* Updates the last requested adjacent TaskFragment token and params.
* @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
*/
void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken,
@NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) {
mLastAdjacentTaskFragment = fragmentToken;
mLastAdjacentParams = params;
}
/**
* Clears the last requested adjacent TaskFragment token and params.
* @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
*/
void clearLastAdjacentTaskFragment() {
final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null
? mController.getContainer(mLastAdjacentTaskFragment)
: null;
mLastAdjacentTaskFragment = null;
mLastAdjacentParams = null;
if (lastAdjacentTaskFragment != null) {
// Clear the previous adjacent TaskFragment as well.
lastAdjacentTaskFragment.clearLastAdjacentTaskFragment();
}
}
/**
* Checks if last requested companion TaskFragment token is equal to the provided value.
* @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
*/
boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) {
return Objects.equals(mLastCompanionTaskFragment, fragmentToken);
}
/**
* Updates the last requested companion TaskFragment token.
* @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
*/
void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) {
mLastCompanionTaskFragment = fragmentToken;
}
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
*/
void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) {
final IBinder activityToken = activity.getActivityToken();
if (hasActivity(activityToken)) {
return;
}
mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken());
}
/**
* Checks if the given activity has requested to be launched in this task fragment.
* @see #addPendingAppearedInRequestedTaskFragmentActivity
*/
boolean isActivityInRequestedTaskFragment(IBinder activityToken) {
if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) {
return true;
}
return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
}
/** Whether contains activities of another process */
boolean hasCrossProcessActivities() {
return mHasCrossProcessActivities;
}
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
}
/** Gets the parent Task. */
@NonNull
TaskContainer getTaskContainer() {
return mTaskContainer;
}
@Nullable
Size getMinDimensions() {
if (mInfo == null) {
return null;
}
int maxMinWidth = mInfo.getMinimumWidth();
int maxMinHeight = mInfo.getMinimumHeight();
for (IBinder activityToken : mPendingAppearedActivities) {
final Activity activity = mController.getActivity(activityToken);
final Size minDimensions = SplitPresenter.getMinDimensions(activity);
if (minDimensions == null) {
continue;
}
maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
}
if (mPendingAppearedIntent != null) {
final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent);
if (minDimensions != null) {
maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
}
}
return new Size(maxMinWidth, maxMinHeight);
}
/** Whether the current TaskFragment is above the {@code other} TaskFragment. */
boolean isAbove(@NonNull TaskFragmentContainer other) {
if (mTaskContainer != other.mTaskContainer) {
throw new IllegalArgumentException(
"Trying to compare two TaskFragments in different Task.");
}
if (this == other) {
throw new IllegalArgumentException("Trying to compare a TaskFragment with itself.");
}
return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
}
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
}
/**
* @return string for this TaskFragmentContainer and includes containers to finish on exit
* based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always
* included in the string, then calling {@link #toString()} on a container that mutually
* finishes with another container would cause a stack overflow.
*/
private String toString(boolean includeContainersToFinishOnExit) {
return "TaskFragmentContainer{"
+ " parentTaskId=" + getTaskId()
+ " token=" + mToken
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
+ containersToFinishOnExitToString() : "")
+ " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
+ " info=" + mInfo
+ "}";
}
private String containersToFinishOnExitToString() {
StringBuilder sb = new StringBuilder("[");
Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator();
while (containerIterator.hasNext()) {
sb.append(containerIterator.next().toString(
false /* includeContainersToFinishOnExit */));
if (containerIterator.hasNext()) {
sb.append(", ");
}
}
return sb.append("]").toString();
}
}