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.IBinder;
28 import android.util.Size;
29 import android.window.TaskFragmentAnimationParams;
30 import android.window.TaskFragmentInfo;
31 import android.window.WindowContainerTransaction;
32 
33 import androidx.annotation.GuardedBy;
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Objects;
44 
45 /**
46  * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
47  * on the server side.
48  */
49 // Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
50 // SplitController.mTaskContainers which is guarded.
51 @SuppressWarnings("GuardedBy")
52 class TaskFragmentContainer {
53     private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
54 
55     @NonNull
56     private final SplitController mController;
57 
58     /**
59      * Client-created token that uniquely identifies the task fragment container instance.
60      */
61     @NonNull
62     private final IBinder mToken;
63 
64     /** Parent leaf Task. */
65     @NonNull
66     private final TaskContainer mTaskContainer;
67 
68     /**
69      * Server-provided task fragment information.
70      */
71     @VisibleForTesting
72     TaskFragmentInfo mInfo;
73 
74     /**
75      * Activity tokens that are being reparented or being started to this container, but haven't
76      * been added to {@link #mInfo} yet.
77      */
78     @VisibleForTesting
79     final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();
80 
81     /**
82      * When this container is created for an {@link Intent} to start within, we store that Intent
83      * until the container becomes non-empty on the server side, so that we can use it to check
84      * rules associated with this container.
85      */
86     @Nullable
87     private Intent mPendingAppearedIntent;
88 
89     /**
90      * The activities that were explicitly requested to be launched in its current TaskFragment,
91      * but haven't been added to {@link #mInfo} yet.
92      */
93     final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>();
94 
95     /** Containers that are dependent on this one and should be completely destroyed on exit. */
96     private final List<TaskFragmentContainer> mContainersToFinishOnExit =
97             new ArrayList<>();
98 
99     /**
100      * Individual associated activity tokens in different containers that should be finished on
101      * exit.
102      */
103     private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
104 
105     /** Indicates whether the container was cleaned up after the last activity was removed. */
106     private boolean mIsFinished;
107 
108     /**
109      * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
110      */
111     private final Rect mLastRequestedBounds = new Rect();
112 
113     /**
114      * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
115      */
116     @WindowingMode
117     private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
118 
119     /**
120      * TaskFragmentAnimationParams that was requested last via
121      * {@link android.window.WindowContainerTransaction}.
122      */
123     @NonNull
124     private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
125 
126     /**
127      * TaskFragment token that was requested last via
128      * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
129      */
130     @Nullable
131     private IBinder mLastAdjacentTaskFragment;
132 
133     /**
134      * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last
135      * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
136      */
137     @Nullable
138     private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams;
139 
140     /**
141      * TaskFragment token that was requested last via
142      * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}.
143      */
144     @Nullable
145     private IBinder mLastCompanionTaskFragment;
146 
147     /**
148      * When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
149      * if it is still empty after the timeout.
150      */
151     @VisibleForTesting
152     @Nullable
153     Runnable mAppearEmptyTimeout;
154 
155     /**
156      * Whether this TaskFragment contains activities of another process/package.
157      */
158     private boolean mHasCrossProcessActivities;
159 
160     /**
161      * Creates a container with an existing activity that will be re-parented to it in a window
162      * container transaction.
163      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
164      */
TaskFragmentContainer(@ullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer)165     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
166             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
167             @NonNull SplitController controller,
168             @Nullable TaskFragmentContainer pairedPrimaryContainer) {
169         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
170                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
171             throw new IllegalArgumentException(
172                     "One and only one of pending activity and intent must be non-null");
173         }
174         mController = controller;
175         mToken = new Binder("TaskFragmentContainer");
176         mTaskContainer = taskContainer;
177         if (pairedPrimaryContainer != null) {
178             // The TaskFragment will be positioned right above the paired container.
179             if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
180                 throw new IllegalArgumentException(
181                         "pairedPrimaryContainer must be in the same Task");
182             }
183             final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
184             taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
185         } else if (pendingAppearedActivity != null) {
186             // The TaskFragment will be positioned right above the pending appeared Activity. If any
187             // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
188             // the pending Intent hasn't been created yet, so the new Activity should be below the
189             // empty TaskFragment.
190             final List<TaskFragmentContainer> containers =
191                     taskContainer.getTaskFragmentContainers();
192             int i = containers.size() - 1;
193             for (; i >= 0; i--) {
194                 final TaskFragmentContainer container = containers.get(i);
195                 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
196                     break;
197                 }
198             }
199             taskContainer.addTaskFragmentContainer(i + 1, this);
200         } else {
201             taskContainer.addTaskFragmentContainer(this);
202         }
203         if (pendingAppearedActivity != null) {
204             addPendingAppearedActivity(pendingAppearedActivity);
205         }
206         mPendingAppearedIntent = pendingAppearedIntent;
207     }
208 
209     /**
210      * Returns the client-created token that uniquely identifies this container.
211      */
212     @NonNull
getTaskFragmentToken()213     IBinder getTaskFragmentToken() {
214         return mToken;
215     }
216 
217     /** List of non-finishing activities that belong to this container and live in this process. */
218     @NonNull
collectNonFinishingActivities()219     List<Activity> collectNonFinishingActivities() {
220         final List<Activity> allActivities = new ArrayList<>();
221         if (mInfo != null) {
222             // Add activities reported from the server.
223             for (IBinder token : mInfo.getActivities()) {
224                 final Activity activity = mController.getActivity(token);
225                 if (activity != null && !activity.isFinishing()) {
226                     allActivities.add(activity);
227                 }
228             }
229         }
230 
231         // Add the re-parenting activity, in case the server has not yet reported the task
232         // fragment info update with it placed in this container. We still want to apply rules
233         // in this intermediate state.
234         // Place those on top of the list since they will be on the top after reported from the
235         // server.
236         for (IBinder token : mPendingAppearedActivities) {
237             final Activity activity = mController.getActivity(token);
238             if (activity != null && !activity.isFinishing()) {
239                 allActivities.add(activity);
240             }
241         }
242         return allActivities;
243     }
244 
245     /** Whether this TaskFragment is visible. */
isVisible()246     boolean isVisible() {
247         return mInfo != null && mInfo.isVisible();
248     }
249 
250     /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
isInIntermediateState()251     boolean isInIntermediateState() {
252         if (mInfo == null) {
253             // Haven't received onTaskFragmentAppeared event.
254             return true;
255         }
256         if (mInfo.isEmpty()) {
257             // Empty TaskFragment will be removed or will have activity launched into it soon.
258             return true;
259         }
260         if (!mPendingAppearedActivities.isEmpty()) {
261             // Reparented activity hasn't appeared.
262             return true;
263         }
264         // Check if there is any reported activity that is no longer alive.
265         for (IBinder token : mInfo.getActivities()) {
266             final Activity activity = mController.getActivity(token);
267             if (activity == null && !mTaskContainer.isVisible()) {
268                 // Activity can be null if the activity is not attached to process yet. That can
269                 // happen when the activity is started in background.
270                 continue;
271             }
272             if (activity == null || activity.isFinishing()) {
273                 // One of the reported activity is no longer alive, wait for the server update.
274                 return true;
275             }
276         }
277         return false;
278     }
279 
280     @NonNull
toActivityStack()281     ActivityStack toActivityStack() {
282         return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
283     }
284 
285     /** Adds the activity that will be reparented to this container. */
addPendingAppearedActivity(@onNull Activity pendingAppearedActivity)286     void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
287         final IBinder activityToken = pendingAppearedActivity.getActivityToken();
288         if (hasActivity(activityToken)) {
289             return;
290         }
291         // Remove the pending activity from other TaskFragments in case the activity is reparented
292         // again before the server update.
293         mTaskContainer.cleanupPendingAppearedActivity(activityToken);
294         mPendingAppearedActivities.add(activityToken);
295         updateActivityClientRecordTaskFragmentToken(activityToken);
296     }
297 
298     /**
299      * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
300      * activity. This makes sure the token is up-to-date if the activity is relaunched later.
301      */
updateActivityClientRecordTaskFragmentToken(@onNull IBinder activityToken)302     private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
303         final ActivityThread.ActivityClientRecord record = ActivityThread
304                 .currentActivityThread().getActivityClient(activityToken);
305         if (record != null) {
306             record.mTaskFragmentToken = mToken;
307         }
308     }
309 
removePendingAppearedActivity(@onNull IBinder activityToken)310     void removePendingAppearedActivity(@NonNull IBinder activityToken) {
311         mPendingAppearedActivities.remove(activityToken);
312         // Also remove the activity from the mPendingInRequestedTaskFragmentActivities.
313         mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken);
314     }
315 
316     @GuardedBy("mController.mLock")
clearPendingAppearedActivities()317     void clearPendingAppearedActivities() {
318         final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
319         // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
320         // current TaskFragment.
321         mPendingAppearedActivities.clear();
322         mPendingAppearedIntent = null;
323 
324         // For removed pending activities, we need to update the them to their previous containers.
325         for (IBinder activityToken : cleanupActivities) {
326             final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
327                     activityToken);
328             if (curContainer != null) {
329                 curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
330             }
331         }
332     }
333 
334     /** Called when the activity is destroyed. */
onActivityDestroyed(@onNull IBinder activityToken)335     void onActivityDestroyed(@NonNull IBinder activityToken) {
336         removePendingAppearedActivity(activityToken);
337         if (mInfo != null) {
338             // Remove the activity now because there can be a delay before the server callback.
339             mInfo.getActivities().remove(activityToken);
340         }
341         mActivitiesToFinishOnExit.remove(activityToken);
342     }
343 
344     @Nullable
getPendingAppearedIntent()345     Intent getPendingAppearedIntent() {
346         return mPendingAppearedIntent;
347     }
348 
setPendingAppearedIntent(@ullable Intent intent)349     void setPendingAppearedIntent(@Nullable Intent intent) {
350         mPendingAppearedIntent = intent;
351     }
352 
353     /**
354      * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the
355      * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has
356      * running activities).
357      */
clearPendingAppearedIntentIfNeeded(@onNull Intent intent)358     void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) {
359         if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) {
360             return;
361         }
362         mPendingAppearedIntent = null;
363     }
364 
hasActivity(@onNull IBinder activityToken)365     boolean hasActivity(@NonNull IBinder activityToken) {
366         // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
367         // sure the controller considers this container as the one containing the activity.
368         // This is needed when the activity is added as pending appeared activity to one
369         // TaskFragment while it is also an appeared activity in another.
370         return mController.getContainerWithActivity(activityToken) == this;
371     }
372 
373     /** Whether this activity has appeared in the TaskFragment on the server side. */
hasAppearedActivity(@onNull IBinder activityToken)374     boolean hasAppearedActivity(@NonNull IBinder activityToken) {
375         return mInfo != null && mInfo.getActivities().contains(activityToken);
376     }
377 
378     /**
379      * Whether we are waiting for this activity to appear in the TaskFragment on the server side.
380      */
hasPendingAppearedActivity(@onNull IBinder activityToken)381     boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
382         return mPendingAppearedActivities.contains(activityToken);
383     }
384 
getRunningActivityCount()385     int getRunningActivityCount() {
386         int count = mPendingAppearedActivities.size();
387         if (mInfo != null) {
388             count += mInfo.getRunningActivityCount();
389         }
390         return count;
391     }
392 
393     /** Whether we are waiting for the TaskFragment to appear and become non-empty. */
isWaitingActivityAppear()394     boolean isWaitingActivityAppear() {
395         return !mIsFinished && (mInfo == null || mAppearEmptyTimeout != null);
396     }
397 
398     @Nullable
getInfo()399     TaskFragmentInfo getInfo() {
400         return mInfo;
401     }
402 
403     @GuardedBy("mController.mLock")
setInfo(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info)404     void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
405         if (!mIsFinished && mInfo == null && info.isEmpty()) {
406             // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
407             // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
408             // it is still empty after timeout.
409             if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
410                 mAppearEmptyTimeout = () -> {
411                     synchronized (mController.mLock) {
412                         mAppearEmptyTimeout = null;
413                         // Call without the pass-in wct when timeout. We need to applyWct directly
414                         // in this case.
415                         mController.onTaskFragmentAppearEmptyTimeout(this);
416                     }
417                 };
418                 mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
419             } else {
420                 mAppearEmptyTimeout = null;
421                 mController.onTaskFragmentAppearEmptyTimeout(wct, this);
422             }
423         } else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
424             mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
425             mAppearEmptyTimeout = null;
426         }
427 
428         mHasCrossProcessActivities = false;
429         mInfo = info;
430         if (mInfo == null || mInfo.isEmpty()) {
431             return;
432         }
433 
434         // Contains activities of another process if the activities size is not matched to the
435         // running activity count
436         if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
437             mHasCrossProcessActivities = true;
438         }
439 
440         // Only track the pending Intent when the container is empty.
441         mPendingAppearedIntent = null;
442         if (mPendingAppearedActivities.isEmpty()) {
443             return;
444         }
445         // Cleanup activities that were being re-parented
446         List<IBinder> infoActivities = mInfo.getActivities();
447         for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
448             final IBinder activityToken = mPendingAppearedActivities.get(i);
449             if (infoActivities.contains(activityToken)) {
450                 removePendingAppearedActivity(activityToken);
451             }
452         }
453     }
454 
455     @Nullable
getTopNonFinishingActivity()456     Activity getTopNonFinishingActivity() {
457         final List<Activity> activities = collectNonFinishingActivities();
458         return activities.isEmpty() ? null : activities.get(activities.size() - 1);
459     }
460 
461     @Nullable
getBottomMostActivity()462     Activity getBottomMostActivity() {
463         final List<Activity> activities = collectNonFinishingActivities();
464         return activities.isEmpty() ? null : activities.get(0);
465     }
466 
isEmpty()467     boolean isEmpty() {
468         return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty());
469     }
470 
471     /**
472      * Adds a container that should be finished when this container is finished.
473      */
addContainerToFinishOnExit(@onNull TaskFragmentContainer containerToFinish)474     void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) {
475         if (mIsFinished) {
476             return;
477         }
478         mContainersToFinishOnExit.add(containerToFinish);
479     }
480 
481     /**
482      * Removes a container that should be finished when this container is finished.
483      */
removeContainerToFinishOnExit(@onNull TaskFragmentContainer containerToRemove)484     void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
485         removeContainersToFinishOnExit(Collections.singletonList(containerToRemove));
486     }
487 
488     /**
489      * Removes container list that should be finished when this container is finished.
490      */
removeContainersToFinishOnExit(@onNull List<TaskFragmentContainer> containersToRemove)491     void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) {
492         if (mIsFinished) {
493             return;
494         }
495         mContainersToFinishOnExit.removeAll(containersToRemove);
496     }
497 
498     /**
499      * Adds an activity that should be finished when this container is finished.
500      */
addActivityToFinishOnExit(@onNull Activity activityToFinish)501     void addActivityToFinishOnExit(@NonNull Activity activityToFinish) {
502         if (mIsFinished) {
503             return;
504         }
505         mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
506     }
507 
508     /**
509      * Removes an activity that should be finished when this container is finished.
510      */
removeActivityToFinishOnExit(@onNull Activity activityToRemove)511     void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) {
512         if (mIsFinished) {
513             return;
514         }
515         mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
516     }
517 
518     /** Removes all dependencies that should be finished when this container is finished. */
resetDependencies()519     void resetDependencies() {
520         if (mIsFinished) {
521             return;
522         }
523         mContainersToFinishOnExit.clear();
524         mActivitiesToFinishOnExit.clear();
525     }
526 
527     /**
528      * Removes all activities that belong to this process and finishes other containers/activities
529      * configured to finish together.
530      */
531     @GuardedBy("mController.mLock")
finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)532     void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
533             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
534         finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */);
535     }
536 
537     /**
538      * Removes all activities that belong to this process and finishes other containers/activities
539      * configured to finish together.
540      */
finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller, boolean shouldRemoveRecord)541     void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
542             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller,
543             boolean shouldRemoveRecord) {
544         if (!mIsFinished) {
545             mIsFinished = true;
546             if (mAppearEmptyTimeout != null) {
547                 mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
548                 mAppearEmptyTimeout = null;
549             }
550             finishActivities(shouldFinishDependent, presenter, wct, controller);
551         }
552 
553         if (mInfo == null) {
554             // Defer removal the container and wait until TaskFragment appeared.
555             return;
556         }
557 
558         // Cleanup the visuals
559         presenter.deleteTaskFragment(wct, getTaskFragmentToken());
560         if (shouldRemoveRecord) {
561             // Cleanup the records
562             controller.removeContainer(this);
563         }
564         // Clean up task fragment information
565         mInfo = null;
566     }
567 
568     @GuardedBy("mController.mLock")
finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)569     private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
570             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
571         // Finish own activities
572         for (Activity activity : collectNonFinishingActivities()) {
573             if (!activity.isFinishing()
574                     // In case we have requested to reparent the activity to another container (as
575                     // pendingAppeared), we don't want to finish it with this container.
576                     && mController.getContainerWithActivity(activity) == this) {
577                 wct.finishActivity(activity.getActivityToken());
578             }
579         }
580 
581         if (!shouldFinishDependent) {
582             // Always finish the placeholder when the primary is finished.
583             finishPlaceholderIfAny(wct, presenter);
584             return;
585         }
586 
587         // Finish dependent containers
588         for (TaskFragmentContainer container : mContainersToFinishOnExit) {
589             if (container.mIsFinished
590                     || controller.shouldRetainAssociatedContainer(this, container)) {
591                 continue;
592             }
593             container.finish(true /* shouldFinishDependent */, presenter,
594                     wct, controller);
595         }
596         mContainersToFinishOnExit.clear();
597 
598         // Finish associated activities
599         for (IBinder activityToken : mActivitiesToFinishOnExit) {
600             final Activity activity = mController.getActivity(activityToken);
601             if (activity == null || activity.isFinishing()
602                     || controller.shouldRetainAssociatedActivity(this, activity)) {
603                 continue;
604             }
605             wct.finishActivity(activity.getActivityToken());
606         }
607         mActivitiesToFinishOnExit.clear();
608     }
609 
610     @GuardedBy("mController.mLock")
finishPlaceholderIfAny(@onNull WindowContainerTransaction wct, @NonNull SplitPresenter presenter)611     private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
612             @NonNull SplitPresenter presenter) {
613         final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
614         for (TaskFragmentContainer container : mContainersToFinishOnExit) {
615             if (container.mIsFinished) {
616                 continue;
617             }
618             final SplitContainer splitContainer = mController.getActiveSplitForContainers(
619                     this, container);
620             if (splitContainer != null && splitContainer.isPlaceholderContainer()
621                     && splitContainer.getSecondaryContainer() == container) {
622                 // Remove the placeholder secondary TaskFragment.
623                 containersToRemove.add(container);
624             }
625         }
626         mContainersToFinishOnExit.removeAll(containersToRemove);
627         for (TaskFragmentContainer container : containersToRemove) {
628             container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
629         }
630     }
631 
isFinished()632     boolean isFinished() {
633         return mIsFinished;
634     }
635 
636     /**
637      * Checks if last requested bounds are equal to the provided value.
638      * The requested bounds are relative bounds in parent coordinate.
639      * @see WindowContainerTransaction#setRelativeBounds
640      */
areLastRequestedBoundsEqual(@ullable Rect relBounds)641     boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
642         return (relBounds == null && mLastRequestedBounds.isEmpty())
643                 || mLastRequestedBounds.equals(relBounds);
644     }
645 
646     /**
647      * Updates the last requested bounds.
648      * The requested bounds are relative bounds in parent coordinate.
649      * @see WindowContainerTransaction#setRelativeBounds
650      */
setLastRequestedBounds(@ullable Rect relBounds)651     void setLastRequestedBounds(@Nullable Rect relBounds) {
652         if (relBounds == null) {
653             mLastRequestedBounds.setEmpty();
654         } else {
655             mLastRequestedBounds.set(relBounds);
656         }
657     }
658 
659     /**
660      * Checks if last requested windowing mode is equal to the provided value.
661      * @see WindowContainerTransaction#setWindowingMode
662      */
isLastRequestedWindowingModeEqual(@indowingMode int windowingMode)663     boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) {
664         return mLastRequestedWindowingMode == windowingMode;
665     }
666 
667     /**
668      * Updates the last requested windowing mode.
669      * @see WindowContainerTransaction#setWindowingMode
670      */
setLastRequestedWindowingMode(@indowingMode int windowingModes)671     void setLastRequestedWindowingMode(@WindowingMode int windowingModes) {
672         mLastRequestedWindowingMode = windowingModes;
673     }
674 
675     /**
676      * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
677      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
678      */
areLastRequestedAnimationParamsEqual( @onNull TaskFragmentAnimationParams animationParams)679     boolean areLastRequestedAnimationParamsEqual(
680             @NonNull TaskFragmentAnimationParams animationParams) {
681         return mLastAnimationParams.equals(animationParams);
682     }
683 
684     /**
685      * Updates the last requested {@link TaskFragmentAnimationParams}.
686      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
687      */
setLastRequestAnimationParams(@onNull TaskFragmentAnimationParams animationParams)688     void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
689         mLastAnimationParams = animationParams;
690     }
691 
692     /**
693      * Checks if last requested adjacent TaskFragment token and params are equal to the provided
694      * values.
695      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
696      * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
697      */
isLastAdjacentTaskFragmentEqual(@ullable IBinder fragmentToken, @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params)698     boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken,
699             @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) {
700         return Objects.equals(mLastAdjacentTaskFragment, fragmentToken)
701                 && Objects.equals(mLastAdjacentParams, params);
702     }
703 
704     /**
705      * Updates the last requested adjacent TaskFragment token and params.
706      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
707      */
setLastAdjacentTaskFragment(@onNull IBinder fragmentToken, @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params)708     void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken,
709             @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) {
710         mLastAdjacentTaskFragment = fragmentToken;
711         mLastAdjacentParams = params;
712     }
713 
714     /**
715      * Clears the last requested adjacent TaskFragment token and params.
716      * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
717      */
clearLastAdjacentTaskFragment()718     void clearLastAdjacentTaskFragment() {
719         final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null
720                 ? mController.getContainer(mLastAdjacentTaskFragment)
721                 : null;
722         mLastAdjacentTaskFragment = null;
723         mLastAdjacentParams = null;
724         if (lastAdjacentTaskFragment != null) {
725             // Clear the previous adjacent TaskFragment as well.
726             lastAdjacentTaskFragment.clearLastAdjacentTaskFragment();
727         }
728     }
729 
730     /**
731      * Checks if last requested companion TaskFragment token is equal to the provided value.
732      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
733      */
isLastCompanionTaskFragmentEqual(@ullable IBinder fragmentToken)734     boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) {
735         return Objects.equals(mLastCompanionTaskFragment, fragmentToken);
736     }
737 
738     /**
739      * Updates the last requested companion TaskFragment token.
740      * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
741      */
setLastCompanionTaskFragment(@ullable IBinder fragmentToken)742     void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) {
743         mLastCompanionTaskFragment = fragmentToken;
744     }
745 
746     /**
747      * Adds the pending appeared activity that has requested to be launched in this task fragment.
748      * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
749      */
addPendingAppearedInRequestedTaskFragmentActivity(Activity activity)750     void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) {
751         final IBinder activityToken = activity.getActivityToken();
752         if (hasActivity(activityToken)) {
753             return;
754         }
755         mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken());
756     }
757 
758     /**
759      * Checks if the given activity has requested to be launched in this task fragment.
760      * @see #addPendingAppearedInRequestedTaskFragmentActivity
761      */
isActivityInRequestedTaskFragment(IBinder activityToken)762     boolean isActivityInRequestedTaskFragment(IBinder activityToken) {
763         if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) {
764             return true;
765         }
766         return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
767     }
768 
769     /** Whether contains activities of another process */
hasCrossProcessActivities()770     boolean hasCrossProcessActivities() {
771         return mHasCrossProcessActivities;
772     }
773 
774     /** Gets the parent leaf Task id. */
getTaskId()775     int getTaskId() {
776         return mTaskContainer.getTaskId();
777     }
778 
779     /** Gets the parent Task. */
780     @NonNull
getTaskContainer()781     TaskContainer getTaskContainer() {
782         return mTaskContainer;
783     }
784 
785     @Nullable
getMinDimensions()786     Size getMinDimensions() {
787         if (mInfo == null) {
788             return null;
789         }
790         int maxMinWidth = mInfo.getMinimumWidth();
791         int maxMinHeight = mInfo.getMinimumHeight();
792         for (IBinder activityToken : mPendingAppearedActivities) {
793             final Activity activity = mController.getActivity(activityToken);
794             final Size minDimensions = SplitPresenter.getMinDimensions(activity);
795             if (minDimensions == null) {
796                 continue;
797             }
798             maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
799             maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
800         }
801         if (mPendingAppearedIntent != null) {
802             final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent);
803             if (minDimensions != null) {
804                 maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
805                 maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
806             }
807         }
808         return new Size(maxMinWidth, maxMinHeight);
809     }
810 
811     /** Whether the current TaskFragment is above the {@code other} TaskFragment. */
isAbove(@onNull TaskFragmentContainer other)812     boolean isAbove(@NonNull TaskFragmentContainer other) {
813         if (mTaskContainer != other.mTaskContainer) {
814             throw new IllegalArgumentException(
815                     "Trying to compare two TaskFragments in different Task.");
816         }
817         if (this == other) {
818             throw new IllegalArgumentException("Trying to compare a TaskFragment with itself.");
819         }
820         return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
821     }
822 
823     @Override
toString()824     public String toString() {
825         return toString(true /* includeContainersToFinishOnExit */);
826     }
827 
828     /**
829      * @return string for this TaskFragmentContainer and includes containers to finish on exit
830      * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always
831      * included in the string, then calling {@link #toString()} on a container that mutually
832      * finishes with another container would cause a stack overflow.
833      */
toString(boolean includeContainersToFinishOnExit)834     private String toString(boolean includeContainersToFinishOnExit) {
835         return "TaskFragmentContainer{"
836                 + " parentTaskId=" + getTaskId()
837                 + " token=" + mToken
838                 + " topNonFinishingActivity=" + getTopNonFinishingActivity()
839                 + " runningActivityCount=" + getRunningActivityCount()
840                 + " isFinished=" + mIsFinished
841                 + " lastRequestedBounds=" + mLastRequestedBounds
842                 + " pendingAppearedActivities=" + mPendingAppearedActivities
843                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
844                 + containersToFinishOnExitToString() : "")
845                 + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
846                 + " info=" + mInfo
847                 + "}";
848     }
849 
containersToFinishOnExitToString()850     private String containersToFinishOnExitToString() {
851         StringBuilder sb = new StringBuilder("[");
852         Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator();
853         while (containerIterator.hasNext()) {
854             sb.append(containerIterator.next().toString(
855                     false /* includeContainersToFinishOnExit */));
856             if (containerIterator.hasNext()) {
857                 sb.append(", ");
858             }
859         }
860         return sb.append("]").toString();
861     }
862 }
863