1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.window.extensions.embedding;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
20 
21 import android.app.Activity;
22 import android.app.ActivityThread;
23 import android.app.WindowConfiguration.WindowingMode;
24 import android.content.Intent;
25 import android.graphics.Rect;
26 import android.os.Binder;
27 import android.os.Bundle;
28 import android.os.IBinder;
29 import android.util.Size;
30 import android.window.TaskFragmentAnimationParams;
31 import android.window.TaskFragmentInfo;
32 import android.window.WindowContainerTransaction;
33 
34 import androidx.annotation.GuardedBy;
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.window.flags.Flags;
40 
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Objects;
46 
47 /**
48  * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
49  * on the server side.
50  */
51 // Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
52 // SplitController.mTaskContainers which is guarded.
53 @SuppressWarnings("GuardedBy")
54 class TaskFragmentContainer {
55     private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
56 
57     @NonNull
58     private final SplitController mController;
59 
60     /**
61      * Client-created token that uniquely identifies the task fragment container instance.
62      */
63     @NonNull
64     private final IBinder mToken;
65 
66     /** Parent leaf Task. */
67     @NonNull
68     private final TaskContainer mTaskContainer;
69 
70     /**
71      * Server-provided task fragment information.
72      */
73     @VisibleForTesting
74     TaskFragmentInfo mInfo;
75 
76     /**
77      * Activity tokens that are being reparented or being started to this container, but haven't
78      * been added to {@link #mInfo} yet.
79      */
80     @VisibleForTesting
81     final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();
82 
83     /**
84      * When this container is created for an {@link Intent} to start within, we store that Intent
85      * until the container becomes non-empty on the server side, so that we can use it to check
86      * rules associated with this container.
87      */
88     @Nullable
89     private Intent mPendingAppearedIntent;
90 
91     /**
92      * The activities that were explicitly requested to be launched in its current TaskFragment,
93      * but haven't been added to {@link #mInfo} yet.
94      */
95     final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>();
96 
97     /** Containers that are dependent on this one and should be completely destroyed on exit. */
98     private final List<TaskFragmentContainer> mContainersToFinishOnExit =
99             new ArrayList<>();
100 
101     /**
102      * Individual associated activity tokens in different containers that should be finished on
103      * exit.
104      */
105     private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
106 
107     @Nullable
108     private final String mOverlayTag;
109 
110     /**
111      * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
112      * for {@link #isOverlay()} container.
113      */
114     @NonNull
115     private final Bundle mLaunchOptions = new Bundle();
116 
117     /**
118      * The associated {@link Activity#getActivityToken()} of the overlay container.
119      * Must be {@code null} for non-overlay container.
120      * <p>
121      * If an overlay container is associated with an activity, this overlay container will be
122      * dismissed when the associated activity is destroyed. If the overlay container is visible,
123      * activity will be launched on top of the overlay container and expanded to fill the parent
124      * container.
125      */
126     @Nullable
127     private final IBinder mAssociatedActivityToken;
128 
129     /** Indicates whether the container was cleaned up after the last activity was removed. */
130     private boolean mIsFinished;
131 
132     /**
133      * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
134      */
135     private final Rect mLastRequestedBounds = new Rect();
136 
137     /**
138      * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
139      */
140     @WindowingMode
141     private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
142 
143     /**
144      * TaskFragmentAnimationParams that was requested last via
145      * {@link android.window.WindowContainerTransaction}.
146      */
147     @NonNull
148     private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
149 
150     /**
151      * TaskFragment token that was requested last via
152      * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
153      */
154     @Nullable
155     private IBinder mLastAdjacentTaskFragment;
156 
157     /**
158      * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last
159      * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
160      */
161     @Nullable
162     private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams;
163 
164     /**
165      * TaskFragment token that was requested last via
166      * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}.
167      */
168     @Nullable
169     private IBinder mLastCompanionTaskFragment;
170 
171     /**
172      * When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
173      * if it is still empty after the timeout.
174      */
175     @VisibleForTesting
176     @Nullable
177     Runnable mAppearEmptyTimeout;
178 
179     /**
180      * Whether this TaskFragment contains activities of another process/package.
181      */
182     private boolean mHasCrossProcessActivities;
183 
184     /** Whether this TaskFragment enable isolated navigation. */
185     private boolean mIsIsolatedNavigationEnabled;
186 
187     /**
188      * Whether this TaskFragment is pinned.
189      */
190     private boolean mIsPinned;
191 
192     /**
193      * Whether to apply dimming on the parent Task that was requested last.
194      */
195     private boolean mLastDimOnTask;
196 
197     /**
198      * Creates a container with an existing activity that will be re-parented to it in a window
199      * container transaction.
200      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
201      * @param overlayTag                Sets to indicate this taskFragment is an overlay container
202      * @param launchOptions             The launch options to create this container. Must not be
203      *                                  {@code null} for an overlay container
204      * @param associatedActivity        the associated activity of the overlay container. Must be
205      *                                  {@code null} for a non-overlay container.
206      */
TaskFragmentContainer(@ullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, @Nullable Bundle launchOptions, @Nullable Activity associatedActivity)207     private TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
208             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
209             @NonNull SplitController controller,
210             @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
211             @Nullable Bundle launchOptions, @Nullable Activity associatedActivity) {
212         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
213                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
214             throw new IllegalArgumentException(
215                     "One and only one of pending activity and intent must be non-null");
216         }
217         mController = controller;
218         mToken = new Binder("TaskFragmentContainer");
219         mTaskContainer = taskContainer;
220         mOverlayTag = overlayTag;
221         mAssociatedActivityToken = associatedActivity != null
222                 ? associatedActivity.getActivityToken() : null;
223 
224         if (launchOptions != null) {
225             mLaunchOptions.putAll(launchOptions);
226         }
227 
228         if (pairedPrimaryContainer != null) {
229             // The TaskFragment will be positioned right above the paired container.
230             if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
231                 throw new IllegalArgumentException(
232                         "pairedPrimaryContainer must be in the same Task");
233             }
234             final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
235             taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
236         } else if (pendingAppearedActivity != null) {
237             // The TaskFragment will be positioned right above the pending appeared Activity. If any
238             // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
239             // the pending Intent hasn't been created yet, so the new Activity should be below the
240             // empty TaskFragment.
241             final List<TaskFragmentContainer> containers =
242                     taskContainer.getTaskFragmentContainers();
243             int i = containers.size() - 1;
244             for (; i >= 0; i--) {
245                 final TaskFragmentContainer container = containers.get(i);
246                 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
247                     break;
248                 }
249             }
250             taskContainer.addTaskFragmentContainer(i + 1, this);
251         } else {
252             taskContainer.addTaskFragmentContainer(this);
253         }
254         if (pendingAppearedActivity != null) {
255             addPendingAppearedActivity(pendingAppearedActivity);
256         }
257         mPendingAppearedIntent = pendingAppearedIntent;
258 
259         // Save the information necessary for restoring the overlay when needed.
260         if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null
261                 && associatedActivity != null && !associatedActivity.isFinishing()) {
262             final IBinder associatedActivityToken = associatedActivity.getActivityToken();
263             final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken,
264                     launchOptions, pendingAppearedIntent);
265             mController.mOverlayRestoreParams.put(associatedActivityToken, params);
266         }
267     }
268 
269     /**
270      * Returns the client-created token that uniquely identifies this container.
271      */
272     @NonNull
getTaskFragmentToken()273     IBinder getTaskFragmentToken() {
274         return mToken;
275     }
276 
277     /** List of non-finishing activities that belong to this container and live in this process. */
278     @NonNull
collectNonFinishingActivities()279     List<Activity> collectNonFinishingActivities() {
280         final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */);
281         if (activities == null) {
282             throw new IllegalStateException(
283                     "Result activities should never be null when checkIfstable is false.");
284         }
285         return activities;
286     }
287 
288     /**
289      * Collects non-finishing activities that belong to this container and live in this process.
290      *
291      * @param checkIfStable if {@code true}, returns {@code null} when the container is in an
292      *                      intermediate state.
293      * @return List of non-finishing activities that belong to this container and live in this
294      * process, {@code null} if checkIfStable is {@code true} and the container is in an
295      * intermediate state.
296      */
297     @Nullable
collectNonFinishingActivities(boolean checkIfStable)298     List<Activity> collectNonFinishingActivities(boolean checkIfStable) {
299         if (checkIfStable
300                 && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) {
301             return null;
302         }
303 
304         final List<Activity> allActivities = new ArrayList<>();
305         if (mInfo != null) {
306             // Add activities reported from the server.
307             for (IBinder token : mInfo.getActivities()) {
308                 final Activity activity = mController.getActivity(token);
309                 if (activity != null && !activity.isFinishing()) {
310                     allActivities.add(activity);
311                 } else {
312                     if (checkIfStable) {
313                         // Return null except for a special case when the activity is started in
314                         // background.
315                         if (activity == null && !mTaskContainer.isVisible()) {
316                             continue;
317                         }
318                         return null;
319                     }
320                 }
321             }
322         }
323 
324         // Add the re-parenting activity, in case the server has not yet reported the task
325         // fragment info update with it placed in this container. We still want to apply rules
326         // in this intermediate state.
327         // Place those on top of the list since they will be on the top after reported from the
328         // server.
329         for (IBinder token : mPendingAppearedActivities) {
330             final Activity activity = mController.getActivity(token);
331             if (activity != null && !activity.isFinishing()) {
332                 allActivities.add(activity);
333             }
334         }
335         return allActivities;
336     }
337 
338     /** Whether this TaskFragment is visible. */
isVisible()339     boolean isVisible() {
340         return mInfo != null && mInfo.isVisible();
341     }
342 
343     /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
isInIntermediateState()344     boolean isInIntermediateState() {
345         if (mInfo == null) {
346             // Haven't received onTaskFragmentAppeared event.
347             return true;
348         }
349         if (mInfo.isEmpty()) {
350             // Empty TaskFragment will be removed or will have activity launched into it soon.
351             return true;
352         }
353         if (!mPendingAppearedActivities.isEmpty()) {
354             // Reparented activity hasn't appeared.
355             return true;
356         }
357         // Check if there is any reported activity that is no longer alive.
358         for (IBinder token : mInfo.getActivities()) {
359             final Activity activity = mController.getActivity(token);
360             if (activity == null && !mTaskContainer.isVisible()) {
361                 // Activity can be null if the activity is not attached to process yet. That can
362                 // happen when the activity is started in background.
363                 continue;
364             }
365             if (activity == null || activity.isFinishing()) {
366                 // One of the reported activity is no longer alive, wait for the server update.
367                 return true;
368             }
369         }
370         return false;
371     }
372 
373     /**
374      * Returns the ActivityStack representing this container.
375      *
376      * @return ActivityStack representing this container if it is in a stable state. {@code null} if
377      * in an intermediate state.
378      */
379     @Nullable
toActivityStackIfStable()380     ActivityStack toActivityStackIfStable() {
381         final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */);
382         if (activities == null) {
383             return null;
384         }
385         return new ActivityStack(activities, isEmpty(),
386                 ActivityStack.Token.createFromBinder(mToken), mOverlayTag);
387     }
388 
389     /** Adds the activity that will be reparented to this container. */
addPendingAppearedActivity(@onNull Activity pendingAppearedActivity)390     void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
391         final IBinder activityToken = pendingAppearedActivity.getActivityToken();
392         if (hasActivity(activityToken)) {
393             return;
394         }
395         // Remove the pending activity from other TaskFragments in case the activity is reparented
396         // again before the server update.
397         mTaskContainer.cleanupPendingAppearedActivity(activityToken);
398         mPendingAppearedActivities.add(activityToken);
399         updateActivityClientRecordTaskFragmentToken(activityToken);
400     }
401 
402     /**
403      * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
404      * activity. This makes sure the token is up-to-date if the activity is relaunched later.
405      */
updateActivityClientRecordTaskFragmentToken(@onNull IBinder activityToken)406     private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
407         final ActivityThread.ActivityClientRecord record = ActivityThread
408                 .currentActivityThread().getActivityClient(activityToken);
409         if (record != null) {
410             record.mTaskFragmentToken = mToken;
411         }
412     }
413 
removePendingAppearedActivity(@onNull IBinder activityToken)414     void removePendingAppearedActivity(@NonNull IBinder activityToken) {
415         mPendingAppearedActivities.remove(activityToken);
416         // Also remove the activity from the mPendingInRequestedTaskFragmentActivities.
417         mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken);
418     }
419 
420     @GuardedBy("mController.mLock")
clearPendingAppearedActivities()421     void clearPendingAppearedActivities() {
422         final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
423         // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
424         // current TaskFragment.
425         mPendingAppearedActivities.clear();
426         mPendingAppearedIntent = null;
427 
428         // For removed pending activities, we need to update the them to their previous containers.
429         for (IBinder activityToken : cleanupActivities) {
430             final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
431                     activityToken);
432             if (curContainer != null) {
433                 curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
434             }
435         }
436     }
437 
438     /** Called when the activity {@link Activity#isFinishing()} and paused. */
onFinishingActivityPaused(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)439     void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
440                                    @NonNull IBinder activityToken) {
441         finishSelfWithActivityIfNeeded(wct, activityToken);
442     }
443 
444     /** Called when the activity is destroyed. */
onActivityDestroyed(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)445     void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
446                              @NonNull IBinder activityToken) {
447         removePendingAppearedActivity(activityToken);
448         if (mInfo != null) {
449             // Remove the activity now because there can be a delay before the server callback.
450             mInfo.getActivities().remove(activityToken);
451         }
452         mActivitiesToFinishOnExit.remove(activityToken);
453         finishSelfWithActivityIfNeeded(wct, activityToken);
454     }
455 
456     @VisibleForTesting
finishSelfWithActivityIfNeeded(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)457     void finishSelfWithActivityIfNeeded(@NonNull WindowContainerTransaction wct,
458             @NonNull IBinder activityToken) {
459         if (mIsFinished) {
460             return;
461         }
462         // Early return if this container is not an overlay with activity association.
463         if (!isOverlayWithActivityAssociation()) {
464             return;
465         }
466         if (mAssociatedActivityToken == activityToken) {
467             // If the associated activity is destroyed, also finish this overlay container.
468             mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */);
469         }
470     }
471 
472     @Nullable
getPendingAppearedIntent()473     Intent getPendingAppearedIntent() {
474         return mPendingAppearedIntent;
475     }
476 
setPendingAppearedIntent(@ullable Intent intent)477     void setPendingAppearedIntent(@Nullable Intent intent) {
478         mPendingAppearedIntent = intent;
479     }
480 
481     /**
482      * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the
483      * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has
484      * running activities).
485      */
clearPendingAppearedIntentIfNeeded(@onNull Intent intent)486     void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) {
487         if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) {
488             return;
489         }
490         mPendingAppearedIntent = null;
491     }
492 
hasActivity(@onNull IBinder activityToken)493     boolean hasActivity(@NonNull IBinder activityToken) {
494         // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
495         // sure the controller considers this container as the one containing the activity.
496         // This is needed when the activity is added as pending appeared activity to one
497         // TaskFragment while it is also an appeared activity in another.
498         return mTaskContainer.getContainerWithActivity(activityToken) == this;
499     }
500 
501     /** Whether this activity has appeared in the TaskFragment on the server side. */
hasAppearedActivity(@onNull IBinder activityToken)502     boolean hasAppearedActivity(@NonNull IBinder activityToken) {
503         return mInfo != null && mInfo.getActivities().contains(activityToken);
504     }
505 
506     /**
507      * Whether we are waiting for this activity to appear in the TaskFragment on the server side.
508      */
hasPendingAppearedActivity(@onNull IBinder activityToken)509     boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
510         return mPendingAppearedActivities.contains(activityToken);
511     }
512 
getRunningActivityCount()513     int getRunningActivityCount() {
514         int count = mPendingAppearedActivities.size();
515         if (mInfo != null) {
516             count += mInfo.getRunningActivityCount();
517         }
518         return count;
519     }
520 
521     /** Whether we are waiting for the TaskFragment to appear and become non-empty. */
isWaitingActivityAppear()522     boolean isWaitingActivityAppear() {
523         return !mIsFinished && (mInfo == null || mAppearEmptyTimeout != null);
524     }
525 
526     @Nullable
getInfo()527     TaskFragmentInfo getInfo() {
528         return mInfo;
529     }
530 
531     @GuardedBy("mController.mLock")
setInfo(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info)532     void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
533         if (!mIsFinished && mInfo == null && info.isEmpty()) {
534             // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
535             // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
536             // it is still empty after timeout.
537             if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
538                 mAppearEmptyTimeout = () -> {
539                     synchronized (mController.mLock) {
540                         mAppearEmptyTimeout = null;
541                         // Call without the pass-in wct when timeout. We need to applyWct directly
542                         // in this case.
543                         mController.onTaskFragmentAppearEmptyTimeout(this);
544                     }
545                 };
546                 mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
547             } else {
548                 mAppearEmptyTimeout = null;
549                 mController.onTaskFragmentAppearEmptyTimeout(wct, this);
550             }
551         } else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
552             mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
553             mAppearEmptyTimeout = null;
554         }
555 
556         mHasCrossProcessActivities = false;
557         mInfo = info;
558         if (mInfo == null || mInfo.isEmpty()) {
559             return;
560         }
561 
562         // Contains activities of another process if the activities size is not matched to the
563         // running activity count
564         if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
565             mHasCrossProcessActivities = true;
566         }
567 
568         // Only track the pending Intent when the container is empty.
569         mPendingAppearedIntent = null;
570         if (mPendingAppearedActivities.isEmpty()) {
571             return;
572         }
573         // Cleanup activities that were being re-parented
574         List<IBinder> infoActivities = mInfo.getActivities();
575         for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
576             final IBinder activityToken = mPendingAppearedActivities.get(i);
577             if (infoActivities.contains(activityToken)) {
578                 removePendingAppearedActivity(activityToken);
579             }
580         }
581     }
582 
583     @Nullable
getTopNonFinishingActivity()584     Activity getTopNonFinishingActivity() {
585         final List<Activity> activities = collectNonFinishingActivities();
586         return activities.isEmpty() ? null : activities.get(activities.size() - 1);
587     }
588 
589     @Nullable
getBottomMostActivity()590     Activity getBottomMostActivity() {
591         final List<Activity> activities = collectNonFinishingActivities();
592         return activities.isEmpty() ? null : activities.get(0);
593     }
594 
isEmpty()595     boolean isEmpty() {
596         return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty());
597     }
598 
599     /**
600      * Adds a container that should be finished when this container is finished.
601      */
addContainerToFinishOnExit(@onNull TaskFragmentContainer containerToFinish)602     void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) {
603         if (mIsFinished) {
604             return;
605         }
606         mContainersToFinishOnExit.add(containerToFinish);
607     }
608 
609     /**
610      * Removes a container that should be finished when this container is finished.
611      */
removeContainerToFinishOnExit(@onNull TaskFragmentContainer containerToRemove)612     void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
613         removeContainersToFinishOnExit(Collections.singletonList(containerToRemove));
614     }
615 
616     /**
617      * Removes container list that should be finished when this container is finished.
618      */
removeContainersToFinishOnExit(@onNull List<TaskFragmentContainer> containersToRemove)619     void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) {
620         if (mIsFinished) {
621             return;
622         }
623         mContainersToFinishOnExit.removeAll(containersToRemove);
624     }
625 
626     /**
627      * Adds an activity that should be finished when this container is finished.
628      */
addActivityToFinishOnExit(@onNull Activity activityToFinish)629     void addActivityToFinishOnExit(@NonNull Activity activityToFinish) {
630         if (mIsFinished) {
631             return;
632         }
633         mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
634     }
635 
636     /**
637      * Removes an activity that should be finished when this container is finished.
638      */
removeActivityToFinishOnExit(@onNull Activity activityToRemove)639     void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) {
640         if (mIsFinished) {
641             return;
642         }
643         mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
644     }
645 
646     /** Removes all dependencies that should be finished when this container is finished. */
resetDependencies()647     void resetDependencies() {
648         if (mIsFinished) {
649             return;
650         }
651         mContainersToFinishOnExit.clear();
652         mActivitiesToFinishOnExit.clear();
653     }
654 
655     /**
656      * Removes all activities that belong to this process and finishes other containers/activities
657      * configured to finish together.
658      */
659     // Suppress GuardedBy warning because lint ask to mark this method as
660     // @GuardedBy(container.mController.mLock), which is mLock itself
661     @SuppressWarnings("GuardedBy")
662     @GuardedBy("mController.mLock")
finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)663     void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
664             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
665         finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */);
666     }
667 
668     /**
669      * Removes all activities that belong to this process and finishes other containers/activities
670      * configured to finish together.
671      */
finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller, boolean shouldRemoveRecord)672     void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
673             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller,
674             boolean shouldRemoveRecord) {
675         if (!mIsFinished) {
676             mIsFinished = true;
677             if (mAppearEmptyTimeout != null) {
678                 mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
679                 mAppearEmptyTimeout = null;
680             }
681             finishActivities(shouldFinishDependent, presenter, wct, controller);
682         }
683 
684         if (mInfo == null) {
685             // Defer removal the container and wait until TaskFragment appeared.
686             return;
687         }
688 
689         // Cleanup the visuals
690         presenter.deleteTaskFragment(wct, getTaskFragmentToken());
691         if (shouldRemoveRecord) {
692             // Cleanup the records
693             controller.removeContainer(this);
694         }
695         // Clean up task fragment information
696         mInfo = null;
697     }
698 
699     @GuardedBy("mController.mLock")
finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)700     private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
701             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
702         // Finish own activities
703         for (Activity activity : collectNonFinishingActivities()) {
704             if (!activity.isFinishing()
705                     // In case we have requested to reparent the activity to another container (as
706                     // pendingAppeared), we don't want to finish it with this container.
707                     && mController.getContainerWithActivity(activity) == this) {
708                 wct.finishActivity(activity.getActivityToken());
709             }
710         }
711 
712         if (!shouldFinishDependent) {
713             // Always finish the placeholder when the primary is finished.
714             finishPlaceholderIfAny(wct, presenter);
715             return;
716         }
717 
718         // Finish dependent containers
719         for (TaskFragmentContainer container : mContainersToFinishOnExit) {
720             if (container.mIsFinished
721                     || controller.shouldRetainAssociatedContainer(this, container)) {
722                 continue;
723             }
724             container.finish(true /* shouldFinishDependent */, presenter,
725                     wct, controller);
726         }
727         mContainersToFinishOnExit.clear();
728 
729         // Finish associated activities
730         for (IBinder activityToken : mActivitiesToFinishOnExit) {
731             final Activity activity = mController.getActivity(activityToken);
732             if (activity == null || activity.isFinishing()
733                     || controller.shouldRetainAssociatedActivity(this, activity)) {
734                 continue;
735             }
736             wct.finishActivity(activity.getActivityToken());
737         }
738         mActivitiesToFinishOnExit.clear();
739     }
740 
741     @GuardedBy("mController.mLock")
finishPlaceholderIfAny(@onNull WindowContainerTransaction wct, @NonNull SplitPresenter presenter)742     private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
743             @NonNull SplitPresenter presenter) {
744         final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
745         for (TaskFragmentContainer container : mContainersToFinishOnExit) {
746             if (container.mIsFinished) {
747                 continue;
748             }
749             final SplitContainer splitContainer = mController.getActiveSplitForContainers(
750                     this, container);
751             if (splitContainer != null && splitContainer.isPlaceholderContainer()
752                     && splitContainer.getSecondaryContainer() == container) {
753                 // Remove the placeholder secondary TaskFragment.
754                 containersToRemove.add(container);
755             }
756         }
757         mContainersToFinishOnExit.removeAll(containersToRemove);
758         for (TaskFragmentContainer container : containersToRemove) {
759             container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
760         }
761     }
762 
isFinished()763     boolean isFinished() {
764         return mIsFinished;
765     }
766 
767     /**
768      * Checks if last requested bounds are equal to the provided value.
769      * The requested bounds are relative bounds in parent coordinate.
770      * @see WindowContainerTransaction#setRelativeBounds
771      */
areLastRequestedBoundsEqual(@ullable Rect relBounds)772     boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
773         return (relBounds == null && mLastRequestedBounds.isEmpty())
774                 || mLastRequestedBounds.equals(relBounds);
775     }
776 
777     /**
778      * Updates the last requested bounds.
779      * The requested bounds are relative bounds in parent coordinate.
780      * @see WindowContainerTransaction#setRelativeBounds
781      */
setLastRequestedBounds(@ullable Rect relBounds)782     void setLastRequestedBounds(@Nullable Rect relBounds) {
783         if (relBounds == null) {
784             mLastRequestedBounds.setEmpty();
785         } else {
786             mLastRequestedBounds.set(relBounds);
787         }
788     }
789 
getLastRequestedBounds()790     @NonNull Rect getLastRequestedBounds() {
791         return mLastRequestedBounds;
792     }
793 
794     /**
795      * Checks if last requested windowing mode is equal to the provided value.
796      * @see WindowContainerTransaction#setWindowingMode
797      */
isLastRequestedWindowingModeEqual(@indowingMode int windowingMode)798     boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) {
799         return mLastRequestedWindowingMode == windowingMode;
800     }
801 
802     /**
803      * Updates the last requested windowing mode.
804      * @see WindowContainerTransaction#setWindowingMode
805      */
setLastRequestedWindowingMode(@indowingMode int windowingModes)806     void setLastRequestedWindowingMode(@WindowingMode int windowingModes) {
807         mLastRequestedWindowingMode = windowingModes;
808     }
809 
810     /**
811      * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
812      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
813      */
areLastRequestedAnimationParamsEqual( @onNull TaskFragmentAnimationParams animationParams)814     boolean areLastRequestedAnimationParamsEqual(
815             @NonNull TaskFragmentAnimationParams animationParams) {
816         return mLastAnimationParams.equals(animationParams);
817     }
818 
819     /**
820      * Updates the last requested {@link TaskFragmentAnimationParams}.
821      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
822      */
setLastRequestAnimationParams(@onNull TaskFragmentAnimationParams animationParams)823     void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
824         mLastAnimationParams = animationParams;
825     }
826 
827     /**
828      * Checks if last requested adjacent TaskFragment token and params are equal to the provided
829      * values.
830      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
831      * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
832      */
isLastAdjacentTaskFragmentEqual(@ullable IBinder fragmentToken, @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params)833     boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken,
834             @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) {
835         return Objects.equals(mLastAdjacentTaskFragment, fragmentToken)
836                 && Objects.equals(mLastAdjacentParams, params);
837     }
838 
839     /**
840      * Updates the last requested adjacent TaskFragment token and params.
841      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
842      */
setLastAdjacentTaskFragment(@onNull IBinder fragmentToken, @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params)843     void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken,
844             @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) {
845         mLastAdjacentTaskFragment = fragmentToken;
846         mLastAdjacentParams = params;
847     }
848 
849     /**
850      * Clears the last requested adjacent TaskFragment token and params.
851      * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
852      */
clearLastAdjacentTaskFragment()853     void clearLastAdjacentTaskFragment() {
854         final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null
855                 ? mController.getContainer(mLastAdjacentTaskFragment)
856                 : null;
857         mLastAdjacentTaskFragment = null;
858         mLastAdjacentParams = null;
859         if (lastAdjacentTaskFragment != null) {
860             // Clear the previous adjacent TaskFragment as well.
861             lastAdjacentTaskFragment.clearLastAdjacentTaskFragment();
862         }
863     }
864 
865     /**
866      * Checks if last requested companion TaskFragment token is equal to the provided value.
867      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
868      */
isLastCompanionTaskFragmentEqual(@ullable IBinder fragmentToken)869     boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) {
870         return Objects.equals(mLastCompanionTaskFragment, fragmentToken);
871     }
872 
873     /**
874      * Updates the last requested companion TaskFragment token.
875      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
876      */
setLastCompanionTaskFragment(@ullable IBinder fragmentToken)877     void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) {
878         mLastCompanionTaskFragment = fragmentToken;
879     }
880 
881     /** Returns whether to enable isolated navigation or not. */
isIsolatedNavigationEnabled()882     boolean isIsolatedNavigationEnabled() {
883         return mIsIsolatedNavigationEnabled;
884     }
885 
886     /** Sets whether to enable isolated navigation or not. */
setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled)887     void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
888         mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
889     }
890 
891     /**
892      * Returns whether this container is pinned.
893      *
894      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED
895      */
isPinned()896     boolean isPinned() {
897         return mIsPinned;
898     }
899 
900     /**
901      * Sets whether to pin this container or not.
902      *
903      * @see #isPinned()
904      */
setPinned(boolean pinned)905     void setPinned(boolean pinned) {
906         mIsPinned = pinned;
907     }
908 
909     /**
910      * Indicates to skip activity resolving if the activity is from this container.
911      *
912      * @see #isIsolatedNavigationEnabled()
913      * @see #isPinned()
914      */
shouldSkipActivityResolving()915     boolean shouldSkipActivityResolving() {
916         return isIsolatedNavigationEnabled() || isPinned();
917     }
918 
919     /** Sets whether to apply dim on the parent Task. */
setLastDimOnTask(boolean lastDimOnTask)920     void setLastDimOnTask(boolean lastDimOnTask) {
921         mLastDimOnTask = lastDimOnTask;
922     }
923 
924     /** Returns whether to apply dim on the parent Task. */
isLastDimOnTask()925     boolean isLastDimOnTask() {
926         return mLastDimOnTask;
927     }
928 
929     /**
930      * Adds the pending appeared activity that has requested to be launched in this task fragment.
931      * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
932      */
addPendingAppearedInRequestedTaskFragmentActivity(Activity activity)933     void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) {
934         final IBinder activityToken = activity.getActivityToken();
935         if (hasActivity(activityToken)) {
936             return;
937         }
938         mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken());
939     }
940 
941     /**
942      * Checks if the given activity has requested to be launched in this task fragment.
943      * @see #addPendingAppearedInRequestedTaskFragmentActivity
944      */
isActivityInRequestedTaskFragment(IBinder activityToken)945     boolean isActivityInRequestedTaskFragment(IBinder activityToken) {
946         if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) {
947             return true;
948         }
949         return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
950     }
951 
952     /** Whether contains activities of another process */
hasCrossProcessActivities()953     boolean hasCrossProcessActivities() {
954         return mHasCrossProcessActivities;
955     }
956 
957     /** Gets the parent leaf Task id. */
getTaskId()958     int getTaskId() {
959         return mTaskContainer.getTaskId();
960     }
961 
962     /** Gets the parent Task. */
963     @NonNull
getTaskContainer()964     TaskContainer getTaskContainer() {
965         return mTaskContainer;
966     }
967 
968     @Nullable
getMinDimensions()969     Size getMinDimensions() {
970         if (mInfo == null) {
971             return null;
972         }
973         int maxMinWidth = mInfo.getMinimumWidth();
974         int maxMinHeight = mInfo.getMinimumHeight();
975         for (IBinder activityToken : mPendingAppearedActivities) {
976             final Activity activity = mController.getActivity(activityToken);
977             final Size minDimensions = SplitPresenter.getMinDimensions(activity);
978             if (minDimensions == null) {
979                 continue;
980             }
981             maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
982             maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
983         }
984         if (mPendingAppearedIntent != null) {
985             final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent);
986             if (minDimensions != null) {
987                 maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
988                 maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
989             }
990         }
991         return new Size(maxMinWidth, maxMinHeight);
992     }
993 
994     /** Whether the current TaskFragment is above the {@code other} TaskFragment. */
isAbove(@onNull TaskFragmentContainer other)995     boolean isAbove(@NonNull TaskFragmentContainer other) {
996         if (mTaskContainer != other.mTaskContainer) {
997             throw new IllegalArgumentException(
998                     "Trying to compare two TaskFragments in different Task.");
999         }
1000         if (this == other) {
1001             throw new IllegalArgumentException("Trying to compare a TaskFragment with itself.");
1002         }
1003         return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
1004     }
1005 
1006     /** Returns whether this taskFragment container is an overlay container. */
isOverlay()1007     boolean isOverlay() {
1008         return mOverlayTag != null;
1009     }
1010 
1011     /**
1012      * Returns the tag specified in launch options. {@code null} if this taskFragment container is
1013      * not an overlay container.
1014      */
1015     @Nullable
getOverlayTag()1016     String getOverlayTag() {
1017         return mOverlayTag;
1018     }
1019 
1020     /**
1021      * Returns the options that was used to launch this {@link TaskFragmentContainer}.
1022      * {@link Bundle#isEmpty()} means there's no launch option for this container.
1023      * <p>
1024      * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object.
1025      */
1026     @NonNull
getLaunchOptions()1027     Bundle getLaunchOptions() {
1028         return mLaunchOptions;
1029     }
1030 
1031     /**
1032      * Returns the associated Activity token of this overlay container. It must be {@code null}
1033      * for non-overlay container.
1034      * <p>
1035      * If an overlay container is associated with an activity, this overlay container will be
1036      * dismissed when the associated activity is destroyed. If the overlay container is visible,
1037      * activity will be launched on top of the overlay container and expanded to fill the parent
1038      * container.
1039      */
1040     @Nullable
getAssociatedActivityToken()1041     IBinder getAssociatedActivityToken() {
1042         return mAssociatedActivityToken;
1043     }
1044 
1045     /**
1046      * Returns {@code true} if the overlay container should be always on top, which should be
1047      * a non-fill-parent overlay without activity association.
1048      */
isAlwaysOnTopOverlay()1049     boolean isAlwaysOnTopOverlay() {
1050         return isOverlay() && mAssociatedActivityToken == null;
1051     }
1052 
isOverlayWithActivityAssociation()1053     boolean isOverlayWithActivityAssociation() {
1054         return isOverlay() && mAssociatedActivityToken != null;
1055     }
1056 
1057     @Override
toString()1058     public String toString() {
1059         return toString(true /* includeContainersToFinishOnExit */);
1060     }
1061 
1062     /**
1063      * @return string for this TaskFragmentContainer and includes containers to finish on exit
1064      * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always
1065      * included in the string, then calling {@link #toString()} on a container that mutually
1066      * finishes with another container would cause a stack overflow.
1067      */
toString(boolean includeContainersToFinishOnExit)1068     private String toString(boolean includeContainersToFinishOnExit) {
1069         return "TaskFragmentContainer{"
1070                 + " parentTaskId=" + getTaskId()
1071                 + " token=" + mToken
1072                 + " topNonFinishingActivity=" + getTopNonFinishingActivity()
1073                 + " runningActivityCount=" + getRunningActivityCount()
1074                 + " isFinished=" + mIsFinished
1075                 + " overlayTag=" + mOverlayTag
1076                 + " associatedActivityToken=" + mAssociatedActivityToken
1077                 + " lastRequestedBounds=" + mLastRequestedBounds
1078                 + " pendingAppearedActivities=" + mPendingAppearedActivities
1079                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
1080                 + containersToFinishOnExitToString() : "")
1081                 + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
1082                 + " info=" + mInfo
1083                 + "}";
1084     }
1085 
containersToFinishOnExitToString()1086     private String containersToFinishOnExitToString() {
1087         StringBuilder sb = new StringBuilder("[");
1088         Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator();
1089         while (containerIterator.hasNext()) {
1090             sb.append(containerIterator.next().toString(
1091                     false /* includeContainersToFinishOnExit */));
1092             if (containerIterator.hasNext()) {
1093                 sb.append(", ");
1094             }
1095         }
1096         return sb.append("]").toString();
1097     }
1098 
1099     static final class Builder {
1100         @NonNull
1101         private final SplitController mSplitController;
1102 
1103         // The parent Task id of the new TaskFragment.
1104         private final int mTaskId;
1105 
1106         // The activity in the same Task so that we can get the Task bounds if needed.
1107         @NonNull
1108         private final Activity mActivityInTask;
1109 
1110         // The activity that will be reparented to the TaskFragment.
1111         @Nullable
1112         private Activity mPendingAppearedActivity;
1113 
1114         // The Intent that will be started in the TaskFragment.
1115         @Nullable
1116         private Intent mPendingAppearedIntent;
1117 
1118         // The paired primary {@link TaskFragmentContainer}. When it is set, the new container
1119         // will be added right above it.
1120         @Nullable
1121         private TaskFragmentContainer mPairedPrimaryContainer;
1122 
1123         // The launch options bundle to create a container. Must be specified for overlay container.
1124         @Nullable
1125         private Bundle mLaunchOptions;
1126 
1127         // The tag for the new created overlay container. This is required when creating an
1128         // overlay container.
1129         @Nullable
1130         private String mOverlayTag;
1131 
1132         // The associated activity of the overlay container. Must be {@code null} for a
1133         // non-overlay container.
1134         @Nullable
1135         private Activity mAssociatedActivity;
1136 
Builder(@onNull SplitController splitController, int taskId, @Nullable Activity activityInTask)1137         Builder(@NonNull SplitController splitController, int taskId,
1138                 @Nullable Activity activityInTask) {
1139             if (taskId <= 0) {
1140                 throw new IllegalArgumentException("taskId is invalid, " + taskId);
1141             }
1142 
1143             mSplitController = splitController;
1144             mTaskId = taskId;
1145             mActivityInTask = activityInTask;
1146         }
1147 
1148         @NonNull
setPendingAppearedActivity(@ullable Activity pendingAppearedActivity)1149         Builder setPendingAppearedActivity(@Nullable Activity pendingAppearedActivity) {
1150             mPendingAppearedActivity = pendingAppearedActivity;
1151             return this;
1152         }
1153 
1154         @NonNull
setPendingAppearedIntent(@ullable Intent pendingAppearedIntent)1155         Builder setPendingAppearedIntent(@Nullable Intent pendingAppearedIntent) {
1156             mPendingAppearedIntent = pendingAppearedIntent;
1157             return this;
1158         }
1159 
1160         @NonNull
setPairedPrimaryContainer(@ullable TaskFragmentContainer pairedPrimaryContainer)1161         Builder setPairedPrimaryContainer(@Nullable TaskFragmentContainer pairedPrimaryContainer) {
1162             mPairedPrimaryContainer = pairedPrimaryContainer;
1163             return this;
1164         }
1165 
1166         @NonNull
setLaunchOptions(@ullable Bundle launchOptions)1167         Builder setLaunchOptions(@Nullable Bundle launchOptions) {
1168             mLaunchOptions = launchOptions;
1169             return this;
1170         }
1171 
1172         @NonNull
setOverlayTag(@ullable String overlayTag)1173         Builder setOverlayTag(@Nullable String overlayTag) {
1174             mOverlayTag = overlayTag;
1175             return this;
1176         }
1177 
1178         @NonNull
setAssociatedActivity(@ullable Activity associatedActivity)1179         Builder setAssociatedActivity(@Nullable Activity associatedActivity) {
1180             mAssociatedActivity = associatedActivity;
1181             return this;
1182         }
1183 
1184         @NonNull
build()1185         TaskFragmentContainer build() {
1186             if (mOverlayTag != null) {
1187                 Objects.requireNonNull(mLaunchOptions);
1188             } else if (mAssociatedActivity != null) {
1189                 throw new IllegalArgumentException("Associated activity must be null for "
1190                         + "non-overlay activity.");
1191             }
1192 
1193             TaskContainer taskContainer = mSplitController.getTaskContainer(mTaskId);
1194             if (taskContainer == null && mActivityInTask == null) {
1195                 throw new IllegalArgumentException("mActivityInTask must be set.");
1196             }
1197 
1198             if (taskContainer == null) {
1199                 // Adding a TaskContainer if no existed one.
1200                 taskContainer = new TaskContainer(mTaskId, mActivityInTask);
1201                 mSplitController.addTaskContainer(mTaskId, taskContainer);
1202             }
1203 
1204             return new TaskFragmentContainer(mPendingAppearedActivity, mPendingAppearedIntent,
1205                     taskContainer, mSplitController, mPairedPrimaryContainer, mOverlayTag,
1206                     mLaunchOptions, mAssociatedActivity);
1207         }
1208     }
1209 
1210     static class OverlayContainerRestoreParams {
1211         /** The token of the overlay container */
1212         @NonNull
1213         final IBinder mOverlayToken;
1214 
1215         /** The launch options to create this container. */
1216         @NonNull
1217         final Bundle mOptions;
1218 
1219         /** The Intent that used to be started in the overlay container. */
1220         @NonNull
1221         final Intent mIntent;
1222 
OverlayContainerRestoreParams(@onNull IBinder overlayToken, @NonNull Bundle options, @NonNull Intent intent)1223         OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options,
1224                 @NonNull Intent intent) {
1225             mOverlayToken = overlayToken;
1226             mOptions = options;
1227             mIntent = intent;
1228         }
1229     }
1230 }
1231