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