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.ActivityManager.START_SUCCESS;
20 import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
23 import static android.view.Display.DEFAULT_DISPLAY;
24 import static android.view.WindowManager.TRANSIT_CLOSE;
25 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
26 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
27 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
28 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
29 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
30 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE;
31 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
32 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN;
33 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
34 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
35 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
36 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
37 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
38 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
39 
40 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_ACTIVITY_STACK_TOKEN;
41 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
42 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
43 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
44 import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
45 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
46 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
47 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
48 import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
49 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
50 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
51 import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
52 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
53 import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams;
54 
55 import android.annotation.CallbackExecutor;
56 import android.app.Activity;
57 import android.app.ActivityClient;
58 import android.app.ActivityOptions;
59 import android.app.ActivityThread;
60 import android.app.Application;
61 import android.app.Instrumentation;
62 import android.app.servertransaction.ClientTransactionListenerController;
63 import android.content.ComponentName;
64 import android.content.Context;
65 import android.content.Intent;
66 import android.content.res.Configuration;
67 import android.graphics.Rect;
68 import android.os.Bundle;
69 import android.os.Handler;
70 import android.os.IBinder;
71 import android.os.Looper;
72 import android.util.ArrayMap;
73 import android.util.ArraySet;
74 import android.util.Log;
75 import android.util.Pair;
76 import android.util.Size;
77 import android.util.SparseArray;
78 import android.view.WindowMetrics;
79 import android.window.ActivityWindowInfo;
80 import android.window.TaskFragmentAnimationParams;
81 import android.window.TaskFragmentInfo;
82 import android.window.TaskFragmentOperation;
83 import android.window.TaskFragmentParentInfo;
84 import android.window.TaskFragmentTransaction;
85 import android.window.WindowContainerTransaction;
86 
87 import androidx.annotation.GuardedBy;
88 import androidx.annotation.NonNull;
89 import androidx.annotation.Nullable;
90 import androidx.window.common.CommonFoldingFeature;
91 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
92 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
93 import androidx.window.extensions.WindowExtensions;
94 import androidx.window.extensions.core.util.function.Consumer;
95 import androidx.window.extensions.core.util.function.Function;
96 import androidx.window.extensions.core.util.function.Predicate;
97 import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
98 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
99 
100 import com.android.internal.annotations.VisibleForTesting;
101 import com.android.window.flags.Flags;
102 
103 import java.util.ArrayList;
104 import java.util.Collections;
105 import java.util.List;
106 import java.util.Objects;
107 import java.util.Set;
108 import java.util.concurrent.Executor;
109 import java.util.function.BiConsumer;
110 
111 /**
112  * Main controller class that manages split states and presentation.
113  */
114 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
115         ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
116     static final String TAG = "SplitController";
117     static final boolean ENABLE_SHELL_TRANSITIONS = true;
118 
119     // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
120     //  association. It's not set in WM Extensions nor Wm Jetpack library currently.
121     private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
122             "androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity";
123 
124     @VisibleForTesting
125     @GuardedBy("mLock")
126     final SplitPresenter mPresenter;
127 
128     @VisibleForTesting
129     @GuardedBy("mLock")
130     final TransactionManager mTransactionManager;
131 
132     // Currently applied split configuration.
133     @GuardedBy("mLock")
134     private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
135 
136     /**
137      * Stores the token of the associated Activity that maps to the
138      * {@link OverlayContainerRestoreParams} of the most recent created overlay container.
139      */
140     @GuardedBy("mLock")
141     final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>();
142 
143     /**
144      * A developer-defined {@link SplitAttributes} calculator to compute the current
145      * {@link SplitAttributes} with the current device and window states.
146      * It is registered via {@link #setSplitAttributesCalculator(Function)}
147      * and unregistered via {@link #clearSplitAttributesCalculator()}.
148      * This is called when:
149      * <ul>
150      *   <li>{@link SplitPresenter#updateSplitContainer}</li>
151      *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
152      *   <li>Checking whether the place holder should be launched if there's a Activity matches
153      *   {@link SplitPlaceholderRule} </li>
154      * </ul>
155      */
156     @GuardedBy("mLock")
157     @Nullable
158     private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
159 
160     /**
161      * A calculator function to compute {@link ActivityStack} attributes in a task, which is called
162      * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed.
163      */
164     @GuardedBy("mLock")
165     @Nullable
166     private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
167             mActivityStackAttributesCalculator;
168 
169     /**
170      * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
171      * below it.
172      * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
173      * organizer.
174      */
175     @VisibleForTesting
176     @GuardedBy("mLock")
177     final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
178 
179     /** Map from Task id to {@link DividerPresenter} which manages the divider in the Task. */
180     @GuardedBy("mLock")
181     private final SparseArray<DividerPresenter> mDividerPresenters = new SparseArray<>();
182 
183     /** Callback to Jetpack to notify about changes to split states. */
184     @GuardedBy("mLock")
185     @Nullable
186     private Consumer<List<SplitInfo>> mSplitInfoCallback;
187     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
188 
189     /**
190      * Stores callbacks to Jetpack to notify about changes to {@link ActivityStack activityStacks}
191      * and corresponding {@link Executor executors} to dispatch the callback.
192      */
193     @GuardedBy("mLock")
194     @NonNull
195     private final ArrayMap<Consumer<List<ActivityStack>>, Executor> mActivityStackCallbacks =
196             new ArrayMap<>();
197 
198     private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
199 
200     /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
201     @GuardedBy("mLock")
202     @Nullable
203     private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
204             mEmbeddedActivityWindowInfoCallback;
205 
206     /** Listener registered to {@link ClientTransactionListenerController}. */
207     @GuardedBy("mLock")
208     @Nullable
209     private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
210             Flags.activityWindowInfoFlag()
211                     ? this::onActivityWindowInfoChanged
212                     : null;
213 
214     private final Handler mHandler;
215     private final MainThreadExecutor mExecutor;
216     final Object mLock = new Object();
217     private final ActivityStartMonitor mActivityStartMonitor;
218 
SplitController(@onNull WindowLayoutComponentImpl windowLayoutComponent, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer)219     public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
220             @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
221         Log.i(TAG, "Initializing Activity Embedding Controller.");
222         mExecutor = new MainThreadExecutor();
223         mHandler = mExecutor.mHandler;
224         mPresenter = new SplitPresenter(mExecutor, windowLayoutComponent, this);
225         mTransactionManager = new TransactionManager(mPresenter);
226         final ActivityThread activityThread = ActivityThread.currentActivityThread();
227         final Application application = activityThread.getApplication();
228         // Register a callback to be notified about activities being created.
229         application.registerActivityLifecycleCallbacks(new LifecycleCallbacks());
230         // Intercept activity starts to route activities to new containers if necessary.
231         Instrumentation instrumentation = activityThread.getInstrumentation();
232 
233         mActivityStartMonitor = new ActivityStartMonitor();
234         instrumentation.addMonitor(mActivityStartMonitor);
235         foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener());
236     }
237 
238     private class FoldingFeatureListener
239             implements java.util.function.Consumer<List<CommonFoldingFeature>> {
240         @Override
accept(List<CommonFoldingFeature> foldingFeatures)241         public void accept(List<CommonFoldingFeature> foldingFeatures) {
242             synchronized (mLock) {
243                 final TransactionRecord transactionRecord = mTransactionManager
244                         .startNewTransaction();
245                 final WindowContainerTransaction wct = transactionRecord.getTransaction();
246                 for (int i = 0; i < mTaskContainers.size(); i++) {
247                     final TaskContainer taskContainer = mTaskContainers.valueAt(i);
248                     if (!taskContainer.isVisible()) {
249                         continue;
250                     }
251                     if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) {
252                         continue;
253                     }
254                     // TODO(b/238948678): Support reporting display features in all windowing modes.
255                     if (taskContainer.isInMultiWindow()) {
256                         continue;
257                     }
258                     if (taskContainer.isEmpty()) {
259                         continue;
260                     }
261                     updateContainersInTask(wct, taskContainer);
262                 }
263                 // The WCT should be applied and merged to the device state change transition if
264                 // there is one.
265                 transactionRecord.apply(false /* shouldApplyIndependently */);
266             }
267         }
268     }
269 
270     /** Updates the embedding rules applied to future activity launches. */
271     @Override
setEmbeddingRules(@onNull Set<EmbeddingRule> rules)272     public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
273         synchronized (mLock) {
274             Log.i(TAG, "Setting embedding rules. Size: " + rules.size());
275             mSplitRules.clear();
276             mSplitRules.addAll(rules);
277         }
278     }
279 
280     @Override
pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule)281     public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
282         synchronized (mLock) {
283             Log.i(TAG, "Request to pin top activity stack.");
284             final TaskContainer task = getTaskContainer(taskId);
285             if (task == null) {
286                 Log.e(TAG, "Cannot find the task for id: " + taskId);
287                 return false;
288             }
289 
290             final TaskFragmentContainer topContainer =
291                     task.getTopNonFinishingTaskFragmentContainer();
292             // Cannot pin the TaskFragment if no other TaskFragment behind it.
293             if (topContainer == null || task.indexOf(topContainer) <= 0) {
294                 Log.w(TAG, "Cannot find an ActivityStack to pin or split");
295                 return false;
296             }
297             // Abort if the top container is already pinned.
298             if (task.getSplitPinContainer() != null) {
299                 Log.w(TAG, "There is already a pinned ActivityStack.");
300                 return false;
301             }
302 
303             // Find a valid adjacent TaskFragmentContainer
304             final TaskFragmentContainer primaryContainer =
305                     task.getNonFinishingTaskFragmentContainerBelow(topContainer);
306             if (primaryContainer == null) {
307                 Log.w(TAG, "Cannot find another ActivityStack to split");
308                 return false;
309             }
310 
311             // Abort if no space to split.
312             final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
313                     task.getTaskProperties(), splitPinRule,
314                     splitPinRule.getDefaultSplitAttributes(),
315                     getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(),
316                             topContainer.getTopNonFinishingActivity()));
317             if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) {
318                 Log.w(TAG, "No space to split, abort pinning top ActivityStack.");
319                 return false;
320             }
321 
322             // Registers a Split
323             final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
324                     topContainer, splitPinRule, calculatedSplitAttributes);
325             task.addSplitContainer(splitPinContainer);
326 
327             // Updates the Split
328             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
329             final WindowContainerTransaction wct = transactionRecord.getTransaction();
330             mPresenter.updateSplitContainer(splitPinContainer, wct);
331             transactionRecord.apply(false /* shouldApplyIndependently */);
332             updateCallbackIfNecessary();
333             return true;
334         }
335     }
336 
337     @Override
unpinTopActivityStack(int taskId)338     public void unpinTopActivityStack(int taskId) {
339         synchronized (mLock) {
340             Log.i(TAG, "Request to unpin top activity stack.");
341             final TaskContainer task = getTaskContainer(taskId);
342             if (task == null) {
343                 Log.e(TAG, "Cannot find the task to unpin, id: " + taskId);
344                 return;
345             }
346 
347             final SplitPinContainer splitPinContainer = task.getSplitPinContainer();
348             if (splitPinContainer == null) {
349                 Log.e(TAG, "No ActivityStack is pinned.");
350                 return;
351             }
352 
353             // Remove the SplitPinContainer from the task.
354             final TaskFragmentContainer containerToUnpin =
355                     splitPinContainer.getSecondaryContainer();
356             task.removeSplitPinContainer();
357 
358             // Resets the isolated navigation and updates the container.
359             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
360             final WindowContainerTransaction wct = transactionRecord.getTransaction();
361             mPresenter.setTaskFragmentPinned(wct, containerToUnpin, false /* pinned */);
362             updateContainer(wct, containerToUnpin);
363             transactionRecord.apply(false /* shouldApplyIndependently */);
364             updateCallbackIfNecessary();
365         }
366     }
367 
368     @Override
setSplitAttributesCalculator( @onNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator)369     public void setSplitAttributesCalculator(
370             @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
371         synchronized (mLock) {
372             mSplitAttributesCalculator = calculator;
373         }
374     }
375 
376     @Override
clearSplitAttributesCalculator()377     public void clearSplitAttributesCalculator() {
378         synchronized (mLock) {
379             mSplitAttributesCalculator = null;
380         }
381     }
382 
383     @Override
setActivityStackAttributesCalculator( @onNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> calculator)384     public void setActivityStackAttributesCalculator(
385             @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
386                     calculator) {
387         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
388             return;
389         }
390         synchronized (mLock) {
391             mActivityStackAttributesCalculator = calculator;
392         }
393     }
394 
395     @Override
clearActivityStackAttributesCalculator()396     public void clearActivityStackAttributesCalculator() {
397         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
398             return;
399         }
400         synchronized (mLock) {
401             mActivityStackAttributesCalculator = null;
402         }
403     }
404 
405     @GuardedBy("mLock")
406     @Nullable
getSplitAttributesCalculator()407     Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
408         return mSplitAttributesCalculator;
409     }
410 
411     // TODO(b/295993745): remove after we migrate to the bundle approach.
412     @NonNull
setLaunchingActivityStack(@onNull ActivityOptions options, @NonNull IBinder token)413     public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
414             @NonNull IBinder token) {
415         options.setLaunchTaskFragmentToken(token);
416         return options;
417     }
418 
419     @NonNull
420     @GuardedBy("mLock")
421     @VisibleForTesting
getSplitRules()422     List<EmbeddingRule> getSplitRules() {
423         return mSplitRules;
424     }
425 
426     /**
427      * Registers the split organizer callback to notify about changes to active splits.
428      *
429      * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
430      * {@link WindowExtensions#getVendorApiLevel()} 2.
431      */
432     @Deprecated
433     @Override
setSplitInfoCallback( @onNull java.util.function.Consumer<List<SplitInfo>> callback)434     public void setSplitInfoCallback(
435             @NonNull java.util.function.Consumer<List<SplitInfo>> callback) {
436         Consumer<List<SplitInfo>> oemConsumer = callback::accept;
437         setSplitInfoCallback(oemConsumer);
438     }
439 
440     /**
441      * Registers the split organizer callback to notify about changes to active splits.
442      *
443      * @since {@link WindowExtensions#getVendorApiLevel()} 2
444      */
445     @Override
setSplitInfoCallback(Consumer<List<SplitInfo>> callback)446     public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
447         synchronized (mLock) {
448             mSplitInfoCallback = callback;
449             updateSplitInfoCallbackIfNecessary();
450         }
451     }
452 
453     /**
454      * Clears the listener set in {@link SplitController#setSplitInfoCallback(Consumer)}.
455      */
456     @Override
clearSplitInfoCallback()457     public void clearSplitInfoCallback() {
458         synchronized (mLock) {
459             mSplitInfoCallback = null;
460         }
461     }
462 
463     /**
464      * Registers the callback for the {@link ActivityStack} state change.
465      *
466      * @param executor The executor to dispatch the callback.
467      * @param callback The callback for this {@link ActivityStack} state change.
468      */
469     @Override
registerActivityStackCallback(@onNull @allbackExecutor Executor executor, @NonNull Consumer<List<ActivityStack>> callback)470     public void registerActivityStackCallback(@NonNull @CallbackExecutor Executor executor,
471             @NonNull Consumer<List<ActivityStack>> callback) {
472         Objects.requireNonNull(executor);
473         Objects.requireNonNull(callback);
474 
475         synchronized (mLock) {
476             mActivityStackCallbacks.put(callback, executor);
477             updateActivityStackCallbackIfNecessary();
478         }
479     }
480 
481     /** @see #registerActivityStackCallback(Executor, Consumer) */
482     @Override
unregisterActivityStackCallback(@onNull Consumer<List<ActivityStack>> callback)483     public void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) {
484         Objects.requireNonNull(callback);
485 
486         synchronized (mLock) {
487             mActivityStackCallbacks.remove(callback);
488         }
489     }
490 
491     @Override
finishActivityStacks(@onNull Set<IBinder> activityStackTokens)492     public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
493         if (activityStackTokens.isEmpty()) {
494             return;
495         }
496         synchronized (mLock) {
497             // Translate ActivityStack to TaskFragmentContainer.
498             final List<TaskFragmentContainer> pendingFinishingContainers =
499                     activityStackTokens.stream().map(token -> {
500                         synchronized (mLock) {
501                             return getContainer(token);
502                         }
503                     }).filter(Objects::nonNull).toList();
504 
505             if (pendingFinishingContainers.isEmpty()) {
506                 return;
507             }
508             // Start transaction with close transit type.
509             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
510             transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
511             final WindowContainerTransaction wct = transactionRecord.getTransaction();
512 
513             forAllTaskContainers(taskContainer -> {
514                 synchronized (mLock) {
515                     final List<TaskFragmentContainer> containers =
516                             taskContainer.getTaskFragmentContainers();
517                     // Clean up the TaskFragmentContainers by the z-order from the lowest.
518                     for (int i = 0; i < containers.size(); i++) {
519                         final TaskFragmentContainer container = containers.get(i);
520                         if (pendingFinishingContainers.contains(container)) {
521                             // Don't update records here to prevent double invocation.
522                             container.finish(false /* shouldFinishDependant */, mPresenter,
523                                     wct, this, false /* shouldRemoveRecord */);
524                         }
525                     }
526                     // Remove container records.
527                     removeContainers(taskContainer, pendingFinishingContainers);
528                     // Update the change to the server side.
529                     updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
530                 }
531             });
532 
533             // Apply the transaction.
534             transactionRecord.apply(false /* shouldApplyIndependently */);
535         }
536     }
537 
538     @Override
invalidateTopVisibleSplitAttributes()539     public void invalidateTopVisibleSplitAttributes() {
540         synchronized (mLock) {
541             WindowContainerTransaction wct = mTransactionManager.startNewTransaction()
542                     .getTransaction();
543             forAllTaskContainers(taskContainer -> {
544                 synchronized (mLock) {
545                     updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
546                 }
547             });
548             mTransactionManager.getCurrentTransactionRecord()
549                     .apply(false /* shouldApplyIndependently */);
550         }
551     }
552 
553     @GuardedBy("mLock")
forAllTaskContainers(@onNull Consumer<TaskContainer> callback)554     private void forAllTaskContainers(@NonNull Consumer<TaskContainer> callback) {
555         for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
556             callback.accept(mTaskContainers.valueAt(i));
557         }
558     }
559 
560     @Override
updateSplitAttributes(@onNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes)561     public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
562             @NonNull SplitAttributes splitAttributes) {
563         Objects.requireNonNull(splitInfoToken);
564         Objects.requireNonNull(splitAttributes);
565         synchronized (mLock) {
566             final SplitContainer splitContainer = getSplitContainer(splitInfoToken);
567             if (splitContainer == null) {
568                 Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken);
569                 return;
570             }
571             // Override the default split Attributes so that it will be applied
572             // if the SplitContainer is not visible currently.
573             splitContainer.updateDefaultSplitAttributes(splitAttributes);
574 
575             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
576             final WindowContainerTransaction wct = transactionRecord.getTransaction();
577             if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) {
578                 transactionRecord.apply(false /* shouldApplyIndependently */);
579             } else {
580                 // Abort if the SplitContainer wasn't updated.
581                 transactionRecord.abort();
582             }
583         }
584     }
585 
586     @Override
updateActivityStackAttributes(@onNull ActivityStack.Token activityStackToken, @NonNull ActivityStackAttributes attributes)587     public void updateActivityStackAttributes(@NonNull ActivityStack.Token activityStackToken,
588                                               @NonNull ActivityStackAttributes attributes) {
589         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
590             return;
591         }
592         Objects.requireNonNull(activityStackToken);
593         Objects.requireNonNull(attributes);
594 
595         synchronized (mLock) {
596             final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken());
597             if (container == null) {
598                 Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken);
599                 return;
600             }
601             if (!container.isOverlay()) {
602                 Log.w(TAG, "Updating non-overlay container has not supported yet!");
603                 return;
604             }
605 
606             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
607             final WindowContainerTransaction wct = transactionRecord.getTransaction();
608             mPresenter.applyActivityStackAttributes(wct, container, attributes,
609                     container.getMinDimensions());
610             transactionRecord.apply(false /* shouldApplyIndependently */);
611         }
612     }
613 
614     @Override
615     @Nullable
getParentContainerInfo( @onNull ActivityStack.Token activityStackToken)616     public ParentContainerInfo getParentContainerInfo(
617             @NonNull ActivityStack.Token activityStackToken) {
618         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
619             return null;
620         }
621         Objects.requireNonNull(activityStackToken);
622         synchronized (mLock) {
623             final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken());
624             if (container == null) {
625                 return null;
626             }
627             final TaskContainer.TaskProperties properties = container.getTaskContainer()
628                     .getTaskProperties();
629             return mPresenter.createParentContainerInfoFromTaskProperties(properties);
630         }
631     }
632 
633     @Override
634     @Nullable
getActivityStackToken(@onNull String tag)635     public ActivityStack.Token getActivityStackToken(@NonNull String tag) {
636         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
637             return null;
638         }
639         Objects.requireNonNull(tag);
640         synchronized (mLock) {
641             final TaskFragmentContainer taskFragmentContainer =
642                     getContainer(container -> tag.equals(container.getOverlayTag()));
643             if (taskFragmentContainer == null) {
644                 return null;
645             }
646             return ActivityStack.Token.createFromBinder(taskFragmentContainer
647                     .getTaskFragmentToken());
648         }
649     }
650 
651     /**
652      * Called when the transaction is ready so that the organizer can update the TaskFragments based
653      * on the changes in transaction.
654      */
655     @Override
onTransactionReady(@onNull TaskFragmentTransaction transaction)656     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
657         synchronized (mLock) {
658             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
659                     transaction.getTransactionToken());
660             final WindowContainerTransaction wct = transactionRecord.getTransaction();
661             final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
662             for (TaskFragmentTransaction.Change change : changes) {
663                 final int taskId = change.getTaskId();
664                 final TaskFragmentInfo info = change.getTaskFragmentInfo();
665                 switch (change.getType()) {
666                     case TYPE_TASK_FRAGMENT_APPEARED:
667                         mPresenter.updateTaskFragmentInfo(info);
668                         onTaskFragmentAppeared(wct, info);
669                         break;
670                     case TYPE_TASK_FRAGMENT_INFO_CHANGED:
671                         mPresenter.updateTaskFragmentInfo(info);
672                         onTaskFragmentInfoChanged(wct, info);
673                         break;
674                     case TYPE_TASK_FRAGMENT_VANISHED:
675                         mPresenter.removeTaskFragmentInfo(info);
676                         onTaskFragmentVanished(wct, info);
677                         break;
678                     case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
679                         onTaskFragmentParentInfoChanged(wct, taskId,
680                                 change.getTaskFragmentParentInfo());
681                         break;
682                     case TYPE_TASK_FRAGMENT_ERROR:
683                         final Bundle errorBundle = change.getErrorBundle();
684                         final IBinder errorToken = change.getErrorCallbackToken();
685                         final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable(
686                                 KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
687                         final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE);
688                         final Throwable exception = errorBundle.getSerializable(
689                                 KEY_ERROR_CALLBACK_THROWABLE, Throwable.class);
690                         if (errorTaskFragmentInfo != null) {
691                             mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo);
692                         }
693                         onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType,
694                                 exception);
695                         break;
696                     case TYPE_ACTIVITY_REPARENTED_TO_TASK:
697                         final IBinder candidateAssociatedActToken, lastOverlayToken;
698                         if (Flags.fixPipRestoreToOverlay()) {
699                             candidateAssociatedActToken = change.getOtherActivityToken();
700                             lastOverlayToken = change.getTaskFragmentToken();
701                         } else {
702                             candidateAssociatedActToken = lastOverlayToken = null;
703                         }
704                         onActivityReparentedToTask(
705                                 wct,
706                                 taskId,
707                                 change.getActivityIntent(),
708                                 change.getActivityToken(),
709                                 candidateAssociatedActToken,
710                                 lastOverlayToken);
711                         break;
712                     default:
713                         throw new IllegalArgumentException(
714                                 "Unknown TaskFragmentEvent=" + change.getType());
715                 }
716             }
717 
718             // Notify the server, and the server should apply and merge the
719             // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
720             transactionRecord.apply(false /* shouldApplyIndependently */);
721             updateCallbackIfNecessary();
722         }
723     }
724 
725     /**
726      * Called when a TaskFragment is created and organized by this organizer.
727      *
728      * @param wct              The {@link WindowContainerTransaction} to make any changes with if
729      *                         needed.
730      * @param taskFragmentInfo Info of the TaskFragment that is created.
731      */
732     // Suppress GuardedBy warning because lint ask to mark this method as
733     // @GuardedBy(container.mController.mLock), which is mLock itself
734     @SuppressWarnings("GuardedBy")
735     @VisibleForTesting
736     @GuardedBy("mLock")
onTaskFragmentAppeared(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)737     void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
738                                 @NonNull TaskFragmentInfo taskFragmentInfo) {
739         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
740         if (container == null) {
741             return;
742         }
743 
744         container.setInfo(wct, taskFragmentInfo);
745         if (container.isFinished()) {
746             mTransactionManager.getCurrentTransactionRecord()
747                     .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
748             mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
749         } else {
750             // Update with the latest Task configuration.
751             updateContainer(wct, container);
752         }
753     }
754 
755     /**
756      * Called when the status of an organized TaskFragment is changed.
757      *
758      * @param wct              The {@link WindowContainerTransaction} to make any changes with if
759      *                         needed.
760      * @param taskFragmentInfo Info of the TaskFragment that is changed.
761      */
762     // Suppress GuardedBy warning because lint ask to mark this method as
763     // @GuardedBy(container.mController.mLock), which is mLock itself
764     @SuppressWarnings("GuardedBy")
765     @VisibleForTesting
766     @GuardedBy("mLock")
onTaskFragmentInfoChanged(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)767     void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
768             @NonNull TaskFragmentInfo taskFragmentInfo) {
769         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
770         if (container == null) {
771             return;
772         }
773 
774         final boolean wasInPip = isInPictureInPicture(container);
775         container.setInfo(wct, taskFragmentInfo);
776         final boolean isInPip = isInPictureInPicture(container);
777         // Check if there are no running activities - consider the container empty if there are
778         // no non-finishing activities left.
779         if (!taskFragmentInfo.hasRunningActivity()) {
780             if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
781                 // Do not finish the dependents if the last activity is reparented to PiP.
782                 // Instead, the original split should be cleanup, and the dependent may be
783                 // expanded to fullscreen.
784                 mTransactionManager.getCurrentTransactionRecord()
785                         .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
786                 cleanupForEnterPip(wct, container);
787                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
788             } else if (taskFragmentInfo.isTaskClearedForReuse()) {
789                 // Do not finish the dependents if this TaskFragment was cleared due to
790                 // launching activity in the Task.
791                 mTransactionManager.getCurrentTransactionRecord()
792                         .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
793                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
794             } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
795                 // Do not finish the dependents if this TaskFragment was cleared to reorder
796                 // the launching Activity to front of the Task.
797                 mTransactionManager.getCurrentTransactionRecord()
798                         .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
799                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
800             } else if (!container.isWaitingActivityAppear()) {
801                 // Do not finish the container before the expected activity appear until
802                 // timeout.
803                 mTransactionManager.getCurrentTransactionRecord()
804                         .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
805                 mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
806             }
807         } else if (wasInPip && isInPip) {
808             // No update until exit PIP.
809             return;
810         } else if (isInPip) {
811             // Enter PIP.
812             // All overrides will be cleanup.
813             container.setLastRequestedBounds(null /* bounds */);
814             container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
815             container.clearLastAdjacentTaskFragment();
816             container.setLastCompanionTaskFragment(null /* fragmentToken */);
817             container.setLastRequestAnimationParams(TaskFragmentAnimationParams.DEFAULT);
818             cleanupForEnterPip(wct, container);
819         } else if (wasInPip) {
820             // Exit PIP.
821             // Updates the presentation of the container. Expand or launch placeholder if
822             // needed.
823             updateContainer(wct, container);
824         }
825     }
826 
827     /**
828      * Called when an organized TaskFragment is removed.
829      *
830      * @param wct              The {@link WindowContainerTransaction} to make any changes with if
831      *                         needed.
832      * @param taskFragmentInfo Info of the TaskFragment that is removed.
833      */
834     @VisibleForTesting
835     @GuardedBy("mLock")
onTaskFragmentVanished(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)836     void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
837             @NonNull TaskFragmentInfo taskFragmentInfo) {
838         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
839         if (container != null) {
840             // Cleanup if the TaskFragment vanished is not requested by the organizer.
841             removeContainer(container);
842             // Make sure the containers in the Task are up-to-date.
843             updateContainersInTaskIfVisible(wct, container.getTaskId());
844         }
845         cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
846     }
847 
848     /**
849      * Called when the parent leaf Task of organized TaskFragments is changed.
850      * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
851      * transaction.
852      * <p>
853      * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
854      * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
855      * can be an override bounds.
856      *
857      * @param wct        The {@link WindowContainerTransaction} to make any changes with if needed.
858      * @param taskId     Id of the parent Task that is changed.
859      * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
860      */
861     @VisibleForTesting
862     @GuardedBy("mLock")
onTaskFragmentParentInfoChanged(@onNull WindowContainerTransaction wct, int taskId, @NonNull TaskFragmentParentInfo parentInfo)863     void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
864             int taskId, @NonNull TaskFragmentParentInfo parentInfo) {
865         final TaskContainer taskContainer = getTaskContainer(taskId);
866         if (taskContainer == null || taskContainer.isEmpty()) {
867             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
868             return;
869         }
870 
871         if (!parentInfo.isVisible()) {
872             // Only making the TaskContainer invisible and drops the other info, and perform the
873             // update when the next time the Task becomes visible.
874             if (taskContainer.isVisible()) {
875                 taskContainer.setInvisible();
876             }
877             return;
878         }
879 
880         // Checks if container should be updated before apply new parentInfo.
881         final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
882         taskContainer.updateTaskFragmentParentInfo(parentInfo);
883 
884         // The divider need to be updated even if shouldUpdateContainer is false, because the decor
885         // surface may change in TaskFragmentParentInfo, which requires divider update but not
886         // container update.
887         updateDivider(wct, taskContainer);
888 
889         // If the last direct activity of the host task is dismissed and there's an always-on-top
890         // overlay container in the task, the overlay container should also be dismissed.
891         dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer);
892 
893         if (!shouldUpdateContainer) {
894             return;
895         }
896         updateContainersInTask(wct, taskContainer);
897     }
898 
899     @GuardedBy("mLock")
updateContainersInTaskIfVisible(@onNull WindowContainerTransaction wct, int taskId)900     void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) {
901         final TaskContainer taskContainer = getTaskContainer(taskId);
902         if (taskContainer == null) {
903             return;
904         }
905 
906         if (taskContainer.isVisible()) {
907             updateContainersInTask(wct, taskContainer);
908         } else if (Flags.fixNoContainerUpdateWithoutResize()) {
909             // the TaskFragmentContainers need to be updated when the task becomes visible
910             taskContainer.mTaskFragmentContainersNeedsUpdate = true;
911         }
912     }
913 
914     @GuardedBy("mLock")
updateContainersInTask(@onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)915     private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
916             @NonNull TaskContainer taskContainer) {
917         taskContainer.mTaskFragmentContainersNeedsUpdate = false;
918 
919         // Update all TaskFragments in the Task. Make a copy of the list since some may be
920         // removed on updating.
921         final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
922         for (int i = containers.size() - 1; i >= 0; i--) {
923             final TaskFragmentContainer container = containers.get(i);
924             // Wait until onTaskFragmentAppeared to update new container.
925             if (!container.isFinished() && !container.isWaitingActivityAppear()) {
926                 updateContainer(wct, container);
927             }
928         }
929     }
930 
931     /**
932      * Called when an Activity is reparented to the Task with organized TaskFragment. For example,
933      * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
934      * original Task. In this case, we need to notify the organizer so that it can check if the
935      * Activity matches any split rule.
936      *
937      * @param wct            The {@link WindowContainerTransaction} to make any changes with if
938      *                       needed.
939      * @param taskId         The Task that the activity is reparented to.
940      * @param activityIntent The intent that the activity is original launched with.
941      * @param activityToken  If the activity belongs to the same process as the organizer, this
942      *                       will be the actual activity token; if the activity belongs to a
943      *                       different process, the server will generate a temporary token that
944      *                       the organizer can use to reparent the activity through
945      *                       {@link WindowContainerTransaction} if needed.
946      * @param candidateAssociatedActToken The token of the candidate associated-activity.
947      * @param lastOverlayToken The last parent overlay container token.
948      */
949     @VisibleForTesting
950     @GuardedBy("mLock")
onActivityReparentedToTask(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken, @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken)951     void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
952             int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken,
953             @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) {
954         // Reparent the activity to an overlay container if needed.
955         final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams(
956                 candidateAssociatedActToken, lastOverlayToken);
957         if (params != null) {
958             final Activity associatedActivity = getActivity(candidateAssociatedActToken);
959             final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
960                     wct, params.mOptions, params.mIntent, associatedActivity);
961             if (targetContainer != null) {
962                 wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
963                         activityToken);
964                 return;
965             }
966         }
967 
968         // If the activity belongs to the current app process, we treat it as a new activity
969         // launch.
970         final Activity activity = getActivity(activityToken);
971         if (activity != null) {
972             // We don't allow split as primary for new launch because we currently only support
973             // launching to top. We allow split as primary for activity reparent because the
974             // activity may be split as primary before it is reparented out. In that case, we
975             // want to show it as primary again when it is reparented back.
976             if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
977                 // When there is no embedding rule matched, try to place it in the top container
978                 // like a normal launch.
979                 placeActivityInTopContainer(wct, activity);
980             }
981             return;
982         }
983 
984         final TaskContainer taskContainer = getTaskContainer(taskId);
985         if (taskContainer == null || taskContainer.isInPictureInPicture()) {
986             // We don't embed activity when it is in PIP.
987             return;
988         }
989 
990         // If the activity belongs to a different app process, we treat it as starting new
991         // intent, since both actions might result in a new activity that should appear in an
992         // organized TaskFragment.
993         TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
994                 activityIntent, null /* launchingActivity */);
995         if (targetContainer == null) {
996             // When there is no embedding rule matched, try to place it in the top container
997             // like a normal launch.
998             // TODO(b/301034784): Check if it makes sense to place the activity in overlay
999             //  container.
1000             targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
1001         }
1002         if (targetContainer == null) {
1003             return;
1004         }
1005         wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
1006                 activityToken);
1007         // Because the activity does not belong to the organizer process, we wait until
1008         // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
1009     }
1010 
1011     /**
1012      * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code
1013      * associatedActivityToken} associated with and only if data matches the {@code overlayToken}.
1014      * Otherwise, return {@code null}.
1015      */
1016     @VisibleForTesting
1017     @GuardedBy("mLock")
1018     @Nullable
getOverlayContainerRestoreParams( @ullable IBinder associatedActivityToken, @Nullable IBinder overlayToken)1019     OverlayContainerRestoreParams getOverlayContainerRestoreParams(
1020             @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) {
1021         if (!Flags.fixPipRestoreToOverlay()) {
1022             return null;
1023         }
1024 
1025         if (associatedActivityToken == null || overlayToken == null) {
1026             return null;
1027         }
1028 
1029         final TaskFragmentContainer.OverlayContainerRestoreParams params =
1030                 mOverlayRestoreParams.get(associatedActivityToken);
1031         if (params == null) {
1032             return null;
1033         }
1034 
1035         if (params.mOverlayToken != overlayToken) {
1036             // Not the same overlay container, no need to restore.
1037             return null;
1038         }
1039 
1040         final Activity associatedActivity = getActivity(associatedActivityToken);
1041         if (associatedActivity == null || associatedActivity.isFinishing()) {
1042             return null;
1043         }
1044 
1045         return params;
1046     }
1047 
1048     /**
1049      * Called when the {@link WindowContainerTransaction} created with
1050      * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
1051      *
1052      * @param wct                The {@link WindowContainerTransaction} to make any changes with if
1053      *                           needed.
1054      * @param errorCallbackToken token set in
1055      *                           {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
1056      * @param taskFragmentInfo   The {@link TaskFragmentInfo}. This could be {@code null} if no
1057      *                           TaskFragment created.
1058      * @param opType             The {@link WindowContainerTransaction.HierarchyOp} of the failed
1059      *                           transaction operation.
1060      * @param exception          exception from the server side.
1061      */
1062     // Suppress GuardedBy warning because lint ask to mark this method as
1063     // @GuardedBy(container.mController.mLock), which is mLock itself
1064     @SuppressWarnings("GuardedBy")
1065     @VisibleForTesting
1066     @GuardedBy("mLock")
onTaskFragmentError(@onNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception)1067     void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
1068             @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
1069             @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
1070         Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
1071         switch (opType) {
1072             case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
1073             case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
1074                 final TaskFragmentContainer container;
1075                 if (taskFragmentInfo != null) {
1076                     container = getContainer(taskFragmentInfo.getFragmentToken());
1077                 } else {
1078                     container = null;
1079                 }
1080                 if (container == null) {
1081                     break;
1082                 }
1083 
1084                 // Update the latest taskFragmentInfo and perform necessary clean-up
1085                 container.setInfo(wct, taskFragmentInfo);
1086                 container.clearPendingAppearedActivities();
1087                 if (container.isEmpty()) {
1088                     mTransactionManager.getCurrentTransactionRecord()
1089                             .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
1090                     mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
1091                 }
1092                 break;
1093             }
1094             default:
1095                 Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo
1096                         + ", opType = " + opType);
1097         }
1098     }
1099 
1100     /**
1101      * Called on receiving {@link #onTaskFragmentVanished} for cleanup.
1102      */
1103     @GuardedBy("mLock")
cleanupTaskFragment(@onNull IBinder taskFragmentToken)1104     private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
1105         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1106             final TaskContainer taskContainer = mTaskContainers.valueAt(i);
1107             if (!taskContainer.mFinishedContainer.remove(taskFragmentToken)) {
1108                 continue;
1109             }
1110             if (taskContainer.isEmpty()) {
1111                 // Cleanup the TaskContainer if it becomes empty.
1112                 mTaskContainers.remove(taskContainer.getTaskId());
1113                 mDividerPresenters.remove(taskContainer.getTaskId());
1114             }
1115             return;
1116         }
1117     }
1118 
1119     @VisibleForTesting
1120     @GuardedBy("mLock")
onActivityCreated(@onNull WindowContainerTransaction wct, @NonNull Activity launchedActivity)1121     void onActivityCreated(@NonNull WindowContainerTransaction wct,
1122             @NonNull Activity launchedActivity) {
1123         resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
1124         updateCallbackIfNecessary();
1125     }
1126 
1127     /**
1128      * Checks if the new added activity should be routed to a particular container. It can create a
1129      * new container for the activity and a new split container if necessary.
1130      *
1131      * @param activity     the activity that is newly added to the Task.
1132      * @param isOnReparent whether the activity is reparented to the Task instead of new launched.
1133      *                     We only support to split as primary for reparented activity for now.
1134      * @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or
1135      * in a state that the caller shouldn't handle.
1136      */
1137     @VisibleForTesting
1138     @GuardedBy("mLock")
resolveActivityToContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnReparent)1139     boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct,
1140             @NonNull Activity activity, boolean isOnReparent) {
1141         if (isInPictureInPicture(activity) || activity.isFinishing()) {
1142             // We don't embed activity when it is in PIP, or finishing. Return true since we don't
1143             // want any extra handling.
1144             return true;
1145         }
1146 
1147         final TaskFragmentContainer container = getContainerWithActivity(activity);
1148         if (!isOnReparent && container == null
1149                 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
1150             // We can't find the new launched activity in any recorded container, but it is
1151             // currently placed in an embedded TaskFragment. This can happen in two cases:
1152             // 1. the activity is embedded in another app.
1153             // 2. the organizer has already requested to remove the TaskFragment.
1154             // In either case, return true since we don't want any extra handling.
1155             Log.d(TAG, "Activity is in a TaskFragment that is not recorded by the organizer. r="
1156                     + activity);
1157             return true;
1158         }
1159 
1160         if (container != null && container.shouldSkipActivityResolving()) {
1161             return true;
1162         }
1163 
1164         final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
1165         if (!isOnReparent && taskContainer != null
1166                 && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
1167                 != container) {
1168             // Do not resolve if the launched activity is not the top-most container (excludes
1169             // the pinned and overlay container) in the Task.
1170             return true;
1171         }
1172 
1173         // Ensure the top TaskFragments are updated to the right config if activity is resolved
1174         // to a new TaskFragment while pin TF exists.
1175         final boolean handled = resolveActivityToContainerByRule(wct, activity, container,
1176                 isOnReparent);
1177         if (handled && taskContainer != null) {
1178             final SplitPinContainer splitPinContainer = taskContainer.getSplitPinContainer();
1179             if (splitPinContainer != null) {
1180                 final TaskFragmentContainer resolvedContainer = getContainerWithActivity(activity);
1181                 if (resolvedContainer != null && resolvedContainer.getRunningActivityCount() <= 1) {
1182                     updateContainer(wct, splitPinContainer.getSecondaryContainer());
1183                 }
1184             }
1185         }
1186         return handled;
1187     }
1188 
1189     /**
1190      * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
1191      */
1192     @GuardedBy("mLock")
resolveActivityToContainerByRule(@onNull WindowContainerTransaction wct, @NonNull Activity activity, @Nullable TaskFragmentContainer container, boolean isOnReparent)1193     boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
1194             @NonNull Activity activity, @Nullable TaskFragmentContainer container,
1195             boolean isOnReparent) {
1196         /*
1197          * We will check the following to see if there is any embedding rule matched:
1198          * 1. Whether the new launched activity should always expand.
1199          * 2. Whether the new launched activity should launch a placeholder.
1200          * 3. Whether the new launched activity has already been in a split with a rule matched
1201          *    (likely done in #onStartActivity).
1202          * 4. Whether the activity below (if any) should be split with the new launched activity.
1203          * 5. Whether the activity split with the activity below (if any) should be split with the
1204          *    new launched activity.
1205          */
1206 
1207         // 1. Whether the new launched activity should always expand.
1208         if (shouldExpand(activity, null /* intent */)) {
1209             expandActivity(wct, activity);
1210             return true;
1211         }
1212 
1213         // 2. Whether the new launched activity should launch a placeholder.
1214         if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) {
1215             return true;
1216         }
1217 
1218         // Skip resolving the following split-rules if the launched activity has been requested
1219         // to be launched into its current container.
1220         if (container != null && container.isActivityInRequestedTaskFragment(
1221                 activity.getActivityToken())) {
1222             return true;
1223         }
1224 
1225         // 3. Whether the new launched activity has already been in a split with a rule matched.
1226         if (isNewActivityInSplitWithRuleMatched(activity)) {
1227             return true;
1228         }
1229 
1230         // 4. Whether the activity below (if any) should be split with the new launched activity.
1231         final Activity activityBelow = findActivityBelow(activity);
1232         if (activityBelow == null) {
1233             // Can't find any activity below.
1234             return false;
1235         }
1236         if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) {
1237             // Have split rule of [ activityBelow | launchedActivity ].
1238             return true;
1239         }
1240         if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) {
1241             // Have split rule of [ launchedActivity | activityBelow].
1242             return true;
1243         }
1244 
1245         // 5. Whether the activity split with the activity below (if any) should be split with the
1246         //    new launched activity.
1247         final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
1248                 activityBelow);
1249         final SplitContainer topSplit = getActiveSplitForContainer(activityBelowContainer);
1250         if (topSplit == null || !isTopMostSplit(topSplit)) {
1251             // Skip if it is not the topmost split.
1252             return false;
1253         }
1254         final TaskFragmentContainer otherTopContainer =
1255                 topSplit.getPrimaryContainer() == activityBelowContainer
1256                         ? topSplit.getSecondaryContainer()
1257                         : topSplit.getPrimaryContainer();
1258         final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
1259         if (otherTopActivity == null || otherTopActivity == activity) {
1260             // Can't find the top activity on the other split TaskFragment.
1261             return false;
1262         }
1263         if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) {
1264             // Have split rule of [ otherTopActivity | launchedActivity ].
1265             return true;
1266         }
1267         // Have split rule of [ launchedActivity | otherTopActivity].
1268         return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity);
1269     }
1270 
1271     /**
1272      * Places the given activity to the top most TaskFragment in the task if there is any.
1273      */
1274     @GuardedBy("mLock")
1275     @VisibleForTesting
placeActivityInTopContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1276     void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
1277                                      @NonNull Activity activity) {
1278         if (getContainerWithActivity(activity) != null) {
1279             // The activity has already been put in a TaskFragment. This is likely to be done by
1280             // the server when the activity is started.
1281             return;
1282         }
1283         final int taskId = getTaskId(activity);
1284         final TaskContainer taskContainer = getTaskContainer(taskId);
1285         if (taskContainer == null) {
1286             return;
1287         }
1288         // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
1289         final TaskFragmentContainer targetContainer =
1290                 taskContainer.getTopNonFinishingTaskFragmentContainer();
1291         if (targetContainer == null) {
1292             return;
1293         }
1294         targetContainer.addPendingAppearedActivity(activity);
1295         wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
1296                 activity.getActivityToken());
1297     }
1298 
1299     /**
1300      * Starts an activity to side of the launchingActivity with the provided split config.
1301      */
1302     // Suppress GuardedBy warning because lint ask to mark this method as
1303     // @GuardedBy(container.mController.mLock), which is mLock itself
1304     @SuppressWarnings("GuardedBy")
1305     @GuardedBy("mLock")
startActivityToSide(@onNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder)1306     private void startActivityToSide(@NonNull WindowContainerTransaction wct,
1307             @NonNull Activity launchingActivity, @NonNull Intent intent,
1308             @Nullable Bundle options, @NonNull SplitRule sideRule,
1309             @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
1310             boolean isPlaceholder) {
1311         try {
1312             mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
1313                     splitAttributes, isPlaceholder);
1314         } catch (Exception e) {
1315             if (failureCallback != null) {
1316                 failureCallback.accept(e);
1317             }
1318         }
1319     }
1320 
1321     /**
1322      * Expands the given activity by either expanding the TaskFragment it is currently in or putting
1323      * it into a new expanded TaskFragment.
1324      */
1325     @GuardedBy("mLock")
expandActivity(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1326     private void expandActivity(@NonNull WindowContainerTransaction wct,
1327                                 @NonNull Activity activity) {
1328         final TaskFragmentContainer container = getContainerWithActivity(activity);
1329         if (shouldContainerBeExpanded(container)) {
1330             // Make sure that the existing container is expanded.
1331             mPresenter.expandTaskFragment(wct, container);
1332         } else {
1333             // Put activity into a new expanded container.
1334             final TaskFragmentContainer newContainer =
1335                     new TaskFragmentContainer.Builder(this, getTaskId(activity), activity)
1336                             .setPendingAppearedActivity(activity).build();
1337             mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
1338         }
1339     }
1340 
1341     /**
1342      * Whether the given new launched activity is in a split with a rule matched.
1343      */
1344     // Suppress GuardedBy warning because lint asks to mark this method as
1345     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1346     @SuppressWarnings("GuardedBy")
1347     @GuardedBy("mLock")
isNewActivityInSplitWithRuleMatched(@onNull Activity launchedActivity)1348     private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
1349         final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
1350         final SplitContainer splitContainer = getActiveSplitForContainer(container);
1351         if (splitContainer == null) {
1352             return false;
1353         }
1354 
1355         if (container == splitContainer.getPrimaryContainer()) {
1356             // The new launched can be in the primary container when it is starting a new activity
1357             // onCreate.
1358             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
1359             final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent();
1360             if (secondaryIntent != null) {
1361                 // Check with the pending Intent before it is started on the server side.
1362                 // This can happen if the launched Activity start a new Intent to secondary during
1363                 // #onCreated().
1364                 return getSplitRule(launchedActivity, secondaryIntent) != null;
1365             }
1366             final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity();
1367             return secondaryActivity != null
1368                     && getSplitRule(launchedActivity, secondaryActivity) != null;
1369         }
1370 
1371         // Check if the new launched activity is a placeholder.
1372         if (splitContainer.getSplitRule() instanceof SplitPlaceholderRule) {
1373             final SplitPlaceholderRule placeholderRule =
1374                     (SplitPlaceholderRule) splitContainer.getSplitRule();
1375             final ComponentName placeholderName = placeholderRule.getPlaceholderIntent()
1376                     .getComponent();
1377             // TODO(b/232330767): Do we have a better way to check this?
1378             return placeholderName == null
1379                     || placeholderName.equals(launchedActivity.getComponentName())
1380                     || placeholderRule.getPlaceholderIntent().equals(launchedActivity.getIntent());
1381         }
1382 
1383         // Check if the new launched activity should be split with the primary top activity.
1384         final Activity primaryActivity = splitContainer.getPrimaryContainer()
1385                 .getTopNonFinishingActivity();
1386         if (primaryActivity == null) {
1387             return false;
1388         }
1389         /* TODO(b/231845476) we should always respect clearTop.
1390         final SplitPairRule curSplitRule = (SplitPairRule) splitContainer.getSplitRule();
1391         final SplitPairRule splitRule = getSplitRule(primaryActivity, launchedActivity);
1392         return splitRule != null && haveSamePresentation(splitRule, curSplitRule)
1393                 // If the new launched split rule should clear top and it is not the bottom most,
1394                 // it means we should create a new split pair and clear the existing secondary.
1395                 && (!splitRule.shouldClearTop()
1396                 || container.getBottomMostActivity() == launchedActivity);
1397          */
1398         return getSplitRule(primaryActivity, launchedActivity) != null;
1399     }
1400 
1401     /**
1402      * Finds the activity below the given activity.
1403      */
1404     @VisibleForTesting
1405     @Nullable
1406     @GuardedBy("mLock")
findActivityBelow(@onNull Activity activity)1407     Activity findActivityBelow(@NonNull Activity activity) {
1408         Activity activityBelow = null;
1409         final TaskFragmentContainer container = getContainerWithActivity(activity);
1410         // Looking for the activity below from the information we already have if the container
1411         // only embeds activities of the same process because activities of other processes are not
1412         // available in this embedding host process for security concern.
1413         if (container != null && !container.hasCrossProcessActivities()) {
1414             final List<Activity> containerActivities = container.collectNonFinishingActivities();
1415             final int index = containerActivities.indexOf(activity);
1416             if (index > 0) {
1417                 activityBelow = containerActivities.get(index - 1);
1418             }
1419         }
1420         if (activityBelow == null) {
1421             final IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
1422                     activity.getActivityToken());
1423             if (belowToken != null) {
1424                 activityBelow = getActivity(belowToken);
1425             }
1426         }
1427         return activityBelow;
1428     }
1429 
1430     /**
1431      * Checks if there is a rule to split the two activities. If there is one, puts them into split
1432      * and returns {@code true}. Otherwise, returns {@code false}.
1433      */
1434     // Suppress GuardedBy warning because lint ask to mark this method as
1435     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1436     @SuppressWarnings("GuardedBy")
1437     @GuardedBy("mLock")
putActivitiesIntoSplitIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity)1438     private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
1439             @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
1440         final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
1441         if (splitRule == null) {
1442             return false;
1443         }
1444         final TaskFragmentContainer primaryContainer = getContainerWithActivity(
1445                 primaryActivity);
1446         final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
1447         final TaskContainer.TaskProperties taskProperties = mPresenter
1448                 .getTaskProperties(primaryActivity);
1449         final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
1450                 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
1451                 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
1452         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
1453                 && canReuseContainer(splitRule, splitContainer.getSplitRule(),
1454                 taskProperties.getTaskMetrics(),
1455                 calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
1456             // Can launch in the existing secondary container if the rules share the same
1457             // presentation.
1458             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
1459             if (secondaryContainer == getContainerWithActivity(secondaryActivity)) {
1460                 // The activity is already in the target TaskFragment.
1461                 return true;
1462             }
1463             secondaryContainer.addPendingAppearedActivity(secondaryActivity);
1464             if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
1465                     secondaryActivity, null /* secondaryIntent */)
1466                     != RESULT_EXPAND_FAILED_NO_TF_INFO) {
1467                 wct.reparentActivityToTaskFragment(
1468                         secondaryContainer.getTaskFragmentToken(),
1469                         secondaryActivity.getActivityToken());
1470                 return true;
1471             }
1472         }
1473         // Create new split pair.
1474         mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule,
1475                 calculatedSplitAttributes);
1476         return true;
1477     }
1478 
1479     @GuardedBy("mLock")
onActivityConfigurationChanged(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1480     private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct,
1481             @NonNull Activity activity) {
1482         if (activity.isFinishing()) {
1483             // Do nothing if the activity is currently finishing.
1484             return;
1485         }
1486 
1487         if (isInPictureInPicture(activity)) {
1488             // We don't embed activity when it is in PIP.
1489             return;
1490         }
1491         final TaskFragmentContainer currentContainer = getContainerWithActivity(activity);
1492 
1493         if (currentContainer != null) {
1494             // Changes to activities in controllers are handled in
1495             // onTaskFragmentParentInfoChanged
1496             return;
1497         }
1498 
1499         // Check if activity requires a placeholder
1500         launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
1501     }
1502 
1503     @GuardedBy("mLock")
onActivityPaused(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1504     private void onActivityPaused(@NonNull WindowContainerTransaction wct,
1505                                   @NonNull Activity activity) {
1506         // Checks if there's any finishing activity in paused state associate with an overlay
1507         // container. #OnActivityPostDestroyed is a very late signal, which is called after activity
1508         // is not visible and the next activity shows on screen.
1509         if (!activity.isFinishing()) {
1510             // onPaused is triggered without finishing. Early return.
1511             return;
1512         }
1513         // Check if we should dismiss the overlay container with this finishing activity.
1514         final IBinder activityToken = activity.getActivityToken();
1515         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1516             mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken);
1517         }
1518 
1519         mOverlayRestoreParams.remove(activity.getActivityToken());
1520         updateCallbackIfNecessary();
1521     }
1522 
1523     @VisibleForTesting
1524     @GuardedBy("mLock")
onActivityDestroyed(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1525     void onActivityDestroyed(@NonNull WindowContainerTransaction wct, @NonNull Activity activity) {
1526         if (!activity.isFinishing()) {
1527             // onDestroyed is triggered without finishing. This happens when the activity is
1528             // relaunched. In this case, we don't want to cleanup the record.
1529             return;
1530         }
1531         // Remove any pending appeared activity, as the server won't send finished activity to the
1532         // organizer.
1533         final IBinder activityToken = activity.getActivityToken();
1534         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1535             mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken);
1536         }
1537 
1538         mOverlayRestoreParams.remove(activity.getActivityToken());
1539         // We didn't trigger the callback if there were any pending appeared activities, so check
1540         // again after the pending is removed.
1541         updateCallbackIfNecessary();
1542     }
1543 
1544     /**
1545      * Called when we have been waiting too long for the TaskFragment to become non-empty after
1546      * creation.
1547      */
1548     @GuardedBy("mLock")
onTaskFragmentAppearEmptyTimeout(@onNull TaskFragmentContainer container)1549     void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
1550         final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
1551         onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
1552         // Can be applied independently as a timeout callback.
1553         transactionRecord.apply(true /* shouldApplyIndependently */);
1554     }
1555 
1556     /**
1557      * Called when we have been waiting too long for the TaskFragment to become non-empty after
1558      * creation.
1559      */
1560     @GuardedBy("mLock")
onTaskFragmentAppearEmptyTimeout(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1561     void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
1562             @NonNull TaskFragmentContainer container) {
1563         mTransactionManager.getCurrentTransactionRecord()
1564                 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
1565         mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
1566     }
1567 
1568     @Nullable
1569     @GuardedBy("mLock")
resolveStartActivityIntentFromNonActivityContext( @onNull WindowContainerTransaction wct, @NonNull Intent intent)1570     private TaskFragmentContainer resolveStartActivityIntentFromNonActivityContext(
1571             @NonNull WindowContainerTransaction wct, @NonNull Intent intent) {
1572         final int taskCount = mTaskContainers.size();
1573         if (taskCount == 0) {
1574             // We don't have other Activity to check split with.
1575             return null;
1576         }
1577         if (taskCount > 1) {
1578             Log.w(TAG, "App is calling startActivity from a non-Activity context when it has"
1579                     + " more than one Task. If the new launch Activity is in a different process,"
1580                     + " and it is expected to be embedded, please start it from an Activity"
1581                     + " instead.");
1582             return null;
1583         }
1584 
1585         // Check whether the Intent should be embedded in the known Task.
1586         final TaskContainer taskContainer = mTaskContainers.valueAt(0);
1587         if (taskContainer.isInPictureInPicture()
1588                 || taskContainer.getTopNonFinishingActivity(false /* includeOverlay */) == null) {
1589             // We don't embed activity when it is in PIP, or if we can't find any other owner
1590             // activity in non-overlay container in the Task.
1591             return null;
1592         }
1593 
1594         return resolveStartActivityIntent(wct, taskContainer.getTaskId(), intent,
1595                 null /* launchingActivity */);
1596     }
1597 
1598     /**
1599      * When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer}
1600      * that we should reparent the new activity to if there is any embedding rule matched.
1601      *
1602      * @param wct               {@link WindowContainerTransaction} including all the window change
1603      *                          requests. The caller is responsible to call
1604      *                          {@link android.window.TaskFragmentOrganizer#applyTransaction}.
1605      * @param taskId            The Task to start the activity in.
1606      * @param intent            The {@link Intent} for starting the new launched activity.
1607      * @param launchingActivity The {@link Activity} that starts the new activity. We will
1608      *                          prioritize to split the new activity with it if it is not
1609      *                          {@code null}.
1610      * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
1611      * is no embedding rule matched.
1612      */
1613     @VisibleForTesting
1614     @Nullable
1615     @GuardedBy("mLock")
resolveStartActivityIntent(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity)1616     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
1617             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
1618         if (launchingActivity != null) {
1619             final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
1620                     launchingActivity);
1621             if (taskFragmentContainer != null
1622                     && taskFragmentContainer.shouldSkipActivityResolving()) {
1623                 return null;
1624             }
1625             if (isAssociatedWithOverlay(launchingActivity)) {
1626                 // Skip resolving if the launching activity associated with an overlay.
1627                 return null;
1628             }
1629         }
1630 
1631         // Ensure the top TaskFragments are updated to the right config if the intent is resolved
1632         // to a new TaskFragment while pin TF exists.
1633         final TaskFragmentContainer launchingContainer = resolveStartActivityIntentByRule(wct,
1634                 taskId, intent, launchingActivity);
1635         if (launchingContainer != null && launchingContainer.getRunningActivityCount() == 0) {
1636             final SplitPinContainer splitPinContainer =
1637                     launchingContainer.getTaskContainer().getSplitPinContainer();
1638             if (splitPinContainer != null) {
1639                 updateContainer(wct, splitPinContainer.getSecondaryContainer());
1640             }
1641         }
1642         return launchingContainer;
1643     }
1644 
1645     /**
1646      * Resolves the intent to a {@link TaskFragmentContainer} according to the Split-rules.
1647      */
1648     @Nullable
resolveStartActivityIntentByRule(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity)1649     TaskFragmentContainer resolveStartActivityIntentByRule(@NonNull WindowContainerTransaction wct,
1650             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
1651         /*
1652          * We will check the following to see if there is any embedding rule matched:
1653          * 1. Whether the new activity intent should always expand.
1654          * 2. Whether the launching activity (if set) should be split with the new activity intent.
1655          * 3. Whether the top activity (if any) should be split with the new activity intent.
1656          * 4. Whether the top activity (if any) in other split should be split with the new
1657          *    activity intent.
1658          */
1659 
1660         // 1. Whether the new activity intent should always expand.
1661         if (shouldExpand(null /* activity */, intent)) {
1662             return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity);
1663         }
1664 
1665         // 2. Whether the launching activity (if set) should be split with the new activity intent.
1666         if (launchingActivity != null) {
1667             final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
1668                     launchingActivity, intent, true /* respectClearTop */);
1669             if (container != null) {
1670                 return container;
1671             }
1672         }
1673 
1674         // 3. Whether the top activity (if any) should be split with the new activity intent.
1675         final TaskContainer taskContainer = getTaskContainer(taskId);
1676         if (taskContainer == null
1677                 || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) {
1678             // There is no other activity in the Task to check split with.
1679             return null;
1680         }
1681         final TaskFragmentContainer topContainer =
1682                 taskContainer.getTopNonFinishingTaskFragmentContainer();
1683         final Activity topActivity = topContainer.getTopNonFinishingActivity();
1684         if (topActivity != null && topActivity != launchingActivity) {
1685             final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
1686                     topActivity, intent, false /* respectClearTop */);
1687             if (container != null) {
1688                 return container;
1689             }
1690         }
1691 
1692         // 4. Whether the top activity (if any) in other split should be split with the new
1693         //    activity intent.
1694         final SplitContainer topSplit = getActiveSplitForContainer(topContainer);
1695         if (topSplit == null) {
1696             return null;
1697         }
1698         final TaskFragmentContainer otherTopContainer =
1699                 topSplit.getPrimaryContainer() == topContainer
1700                         ? topSplit.getSecondaryContainer()
1701                         : topSplit.getPrimaryContainer();
1702         final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
1703         if (otherTopActivity != null && otherTopActivity != launchingActivity) {
1704             return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent,
1705                     false /* respectClearTop */);
1706         }
1707         return null;
1708     }
1709 
1710     /**
1711      * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
1712      */
1713     @GuardedBy("mLock")
1714     @Nullable
createEmptyExpandedContainer( @onNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity)1715     private TaskFragmentContainer createEmptyExpandedContainer(
1716             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
1717             @Nullable Activity launchingActivity) {
1718         return createEmptyContainer(wct, intent, taskId,
1719                 new ActivityStackAttributes.Builder().build(), launchingActivity,
1720                 null /* overlayTag */, null /* launchOptions */,
1721                 false /* shouldAssociateWithLaunchingActivity */);
1722     }
1723 
1724     /**
1725      * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
1726      * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
1727      * overlay container.
1728      */
1729     @VisibleForTesting
1730     @GuardedBy("mLock")
1731     @Nullable
createEmptyContainer( @onNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @NonNull ActivityStackAttributes activityStackAttributes, @Nullable Activity launchingActivity, @Nullable String overlayTag, @Nullable Bundle launchOptions, boolean associateLaunchingActivity)1732     TaskFragmentContainer createEmptyContainer(
1733             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
1734             @NonNull ActivityStackAttributes activityStackAttributes,
1735             @Nullable Activity launchingActivity, @Nullable String overlayTag,
1736             @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
1737         // We need an activity in the organizer process in the same Task to use as the owner
1738         // activity, as well as to get the Task window info.
1739         final Activity activityInTask;
1740         if (launchingActivity != null) {
1741             activityInTask = launchingActivity;
1742         } else {
1743             final TaskContainer taskContainer = getTaskContainer(taskId);
1744             activityInTask = taskContainer != null
1745                     ? taskContainer.getTopNonFinishingActivity(true /* includeOverlay */)
1746                     : null;
1747         }
1748         if (activityInTask == null) {
1749             // Can't find any activity in the Task that we can use as the owner activity.
1750             return null;
1751         }
1752         final TaskFragmentContainer container =
1753                 new TaskFragmentContainer.Builder(this, taskId, activityInTask)
1754                         .setPendingAppearedIntent(intent)
1755                         .setOverlayTag(overlayTag)
1756                         .setLaunchOptions(launchOptions)
1757                         .setAssociatedActivity(associateLaunchingActivity ? activityInTask : null)
1758                         .build();
1759         final IBinder taskFragmentToken = container.getTaskFragmentToken();
1760         // Note that taskContainer will not exist before calling #newContainer if the container
1761         // is the first embedded TF in the task.
1762         final TaskContainer taskContainer = container.getTaskContainer();
1763         // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
1764         final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
1765                 getMinDimensions(intent), container);
1766         final int windowingMode = taskContainer
1767                 .getWindowingModeForTaskFragment(sanitizedBounds);
1768         mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
1769                 sanitizedBounds, windowingMode);
1770         mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes,
1771                 getMinDimensions(intent));
1772 
1773         return container;
1774     }
1775 
1776     /**
1777      * Returns a container for the new activity intent to launch into as splitting with the primary
1778      * activity.
1779      */
1780     @GuardedBy("mLock")
1781     @Nullable
getSecondaryContainerForSplitIfAny( @onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent intent, boolean respectClearTop)1782     private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
1783             @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
1784             @NonNull Intent intent, boolean respectClearTop) {
1785         final SplitPairRule splitRule = getSplitRule(primaryActivity, intent);
1786         if (splitRule == null) {
1787             return null;
1788         }
1789         final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
1790         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
1791         final TaskContainer.TaskProperties taskProperties = mPresenter
1792                 .getTaskProperties(primaryActivity);
1793         final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
1794         final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
1795                 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
1796                 getActivityIntentMinDimensionsPair(primaryActivity, intent));
1797         if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
1798                 && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
1799                 calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
1800                 // TODO(b/231845476) we should always respect clearTop.
1801                 || !respectClearTop)
1802                 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
1803                 null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
1804             // Can launch in the existing secondary container if the rules share the same
1805             // presentation.
1806             return splitContainer.getSecondaryContainer();
1807         }
1808         // Create a new TaskFragment to split with the primary activity for the new activity.
1809         return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
1810                 splitRule, calculatedSplitAttributes);
1811     }
1812 
1813     /**
1814      * Returns a container that this activity is registered with. An activity can only belong to one
1815      * container, or no container at all.
1816      */
1817     @GuardedBy("mLock")
1818     @Nullable
getContainerWithActivity(@onNull Activity activity)1819     TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
1820         return getContainerWithActivity(activity.getActivityToken());
1821     }
1822 
1823     @GuardedBy("mLock")
1824     @Nullable
getContainerWithActivity(@onNull IBinder activityToken)1825     TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
1826         for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
1827             final TaskFragmentContainer container = mTaskContainers.valueAt(i)
1828                     .getContainerWithActivity(activityToken);
1829             if (container != null) {
1830                 return container;
1831             }
1832         }
1833         return null;
1834     }
1835 
1836     /**
1837      * Creates and registers a new split with the provided containers and configuration. Finishes
1838      * existing secondary containers if found for the given primary container.
1839      */
1840     // Suppress GuardedBy warning because lint ask to mark this method as
1841     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1842     @SuppressWarnings("GuardedBy")
1843     @GuardedBy("mLock")
registerSplit(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes)1844     void registerSplit(@NonNull WindowContainerTransaction wct,
1845             @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
1846             @NonNull TaskFragmentContainer secondaryContainer,
1847             @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
1848         final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
1849                 secondaryContainer, splitRule, splitAttributes);
1850         // Remove container later to prevent pinning escaping toast showing in lock task mode.
1851         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
1852             removeExistingSecondaryContainers(wct, primaryContainer);
1853         }
1854         primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
1855     }
1856 
1857     /**
1858      * Cleanups all the dependencies when the TaskFragment is entering PIP.
1859      */
1860     @GuardedBy("mLock")
cleanupForEnterPip(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1861     private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
1862             @NonNull TaskFragmentContainer container) {
1863         final TaskContainer taskContainer = container.getTaskContainer();
1864         if (taskContainer == null) {
1865             return;
1866         }
1867         final List<SplitContainer> splitsToRemove = new ArrayList<>();
1868         final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
1869         final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
1870         for (SplitContainer splitContainer : splitContainers) {
1871             if (splitContainer.getPrimaryContainer() != container
1872                     && splitContainer.getSecondaryContainer() != container) {
1873                 continue;
1874             }
1875             splitsToRemove.add(splitContainer);
1876             final TaskFragmentContainer splitTf = splitContainer.getPrimaryContainer() == container
1877                     ? splitContainer.getSecondaryContainer()
1878                     : splitContainer.getPrimaryContainer();
1879             containersToUpdate.add(splitTf);
1880             // We don't want the PIP TaskFragment to be removed as a result of any of its dependents
1881             // being removed.
1882             splitTf.removeContainerToFinishOnExit(container);
1883             if (container.getTopNonFinishingActivity() != null) {
1884                 splitTf.removeActivityToFinishOnExit(container.getTopNonFinishingActivity());
1885             }
1886         }
1887         container.resetDependencies();
1888         taskContainer.removeSplitContainers(splitsToRemove);
1889         // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
1890         // since the split is dismissed.
1891         // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
1892         for (TaskFragmentContainer containerToUpdate : containersToUpdate) {
1893             updateContainer(wct, containerToUpdate);
1894         }
1895     }
1896 
1897     /**
1898      * Removes the container from bookkeeping records.
1899      */
removeContainer(@onNull TaskFragmentContainer container)1900     void removeContainer(@NonNull TaskFragmentContainer container) {
1901         removeContainers(container.getTaskContainer(), Collections.singletonList(container));
1902     }
1903 
1904     /**
1905      * Removes containers from bookkeeping records.
1906      */
removeContainers(@onNull TaskContainer taskContainer, @NonNull List<TaskFragmentContainer> containers)1907     void removeContainers(@NonNull TaskContainer taskContainer,
1908             @NonNull List<TaskFragmentContainer> containers) {
1909         // Remove all split containers that included this one
1910         taskContainer.removeTaskFragmentContainers(containers);
1911         // Marked as a pending removal which will be removed after it is actually removed on the
1912         // server side (#onTaskFragmentVanished).
1913         // In this way, we can keep track of the Task bounds until we no longer have any
1914         // TaskFragment there.
1915         taskContainer.mFinishedContainer.addAll(containers.stream().map(
1916                 TaskFragmentContainer::getTaskFragmentToken).toList());
1917 
1918         // Cleanup any split references.
1919         final List<SplitContainer> containersToRemove = new ArrayList<>();
1920         final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
1921         for (SplitContainer splitContainer : splitContainers) {
1922             if (containersToRemove.contains(splitContainer)) {
1923                 // Don't need to check because it has been in the remove list.
1924                 continue;
1925             }
1926             if (containers.stream().anyMatch(container ->
1927                     splitContainer.getPrimaryContainer().equals(container)
1928                             || splitContainer.getSecondaryContainer().equals(container))) {
1929                 containersToRemove.add(splitContainer);
1930             }
1931         }
1932         taskContainer.removeSplitContainers(containersToRemove);
1933 
1934         // Cleanup any dependent references.
1935         final List<TaskFragmentContainer> taskFragmentContainers =
1936                 taskContainer.getTaskFragmentContainers();
1937         for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) {
1938             containerToUpdate.removeContainersToFinishOnExit(containers);
1939         }
1940     }
1941 
1942     /**
1943      * Removes a secondary container for the given primary container if an existing split is
1944      * already registered.
1945      */
1946     // Suppress GuardedBy warning because lint asks to mark this method as
1947     // @GuardedBy(existingSplitContainer.getSecondaryContainer().mController.mLock), which is mLock
1948     // itself
1949     @SuppressWarnings("GuardedBy")
1950     @GuardedBy("mLock")
removeExistingSecondaryContainers(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer)1951     private void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct,
1952             @NonNull TaskFragmentContainer primaryContainer) {
1953         // If the primary container was already in a split - remove the secondary container that
1954         // is now covered by the new one that replaced it.
1955         final SplitContainer existingSplitContainer = getActiveSplitForContainer(
1956                 primaryContainer);
1957         if (existingSplitContainer == null
1958                 || primaryContainer == existingSplitContainer.getSecondaryContainer()) {
1959             return;
1960         }
1961 
1962         // If the secondary container is pinned, it should not be removed.
1963         final SplitContainer activeContainer =
1964                 getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
1965         if (activeContainer instanceof SplitPinContainer) {
1966             return;
1967         }
1968 
1969         existingSplitContainer.getSecondaryContainer().finish(
1970                 false /* shouldFinishDependent */, mPresenter, wct, this);
1971     }
1972 
1973     /**
1974      * Updates the presentation of the container. If the container is part of the split or should
1975      * have a placeholder, it will also update the other part of the split.
1976      */
1977     @GuardedBy("mLock")
updateContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1978     void updateContainer(@NonNull WindowContainerTransaction wct,
1979             @NonNull TaskFragmentContainer container) {
1980         if (!container.getTaskContainer().isVisible()) {
1981             // Wait until the Task is visible to avoid unnecessary update when the Task is still in
1982             // background.
1983             return;
1984         }
1985 
1986         if (container.isOverlay()) {
1987             updateOverlayContainer(wct, container);
1988             return;
1989         }
1990 
1991         if (launchPlaceholderIfNecessary(wct, container)) {
1992             // Placeholder was launched, the positions will be updated when the activity is added
1993             // to the secondary container.
1994             return;
1995         }
1996         if (shouldContainerBeExpanded(container)) {
1997             if (container.getInfo() != null) {
1998                 mPresenter.expandTaskFragment(wct, container);
1999             }
2000             // If the info is not available yet the task fragment will be expanded when it's ready
2001             return;
2002         }
2003         final SplitContainer splitContainer = getActiveSplitForContainer(container);
2004         if (splitContainer == null) {
2005             return;
2006         }
2007 
2008         updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
2009     }
2010 
2011 
2012     @VisibleForTesting
2013     // Suppress GuardedBy warning because lint ask to mark this method as
2014     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
2015     @SuppressWarnings("GuardedBy")
2016     @GuardedBy("mLock")
updateOverlayContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)2017     void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
2018             @NonNull TaskFragmentContainer container) {
2019         final TaskContainer taskContainer = container.getTaskContainer();
2020 
2021         if (dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer)) {
2022             return;
2023         }
2024 
2025         if (mActivityStackAttributesCalculator == null) {
2026             Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container"
2027                     + " can not be updated.");
2028             return;
2029         }
2030 
2031         if (mActivityStackAttributesCalculator != null) {
2032             final ActivityStackAttributesCalculatorParams params =
2033                     new ActivityStackAttributesCalculatorParams(
2034                             mPresenter.createParentContainerInfoFromTaskProperties(
2035                                     taskContainer.getTaskProperties()),
2036                             container.getOverlayTag(),
2037                             container.getLaunchOptions());
2038             final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
2039                     .apply(params);
2040             mPresenter.applyActivityStackAttributes(wct, container, attributes,
2041                     container.getMinDimensions());
2042         }
2043     }
2044 
2045     /**
2046      * Dismisses {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the {@code taskContainer}
2047      * if needed.
2048      */
2049     @GuardedBy("mLock")
dismissAlwaysOnTopOverlayIfNeeded(@onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)2050     private boolean dismissAlwaysOnTopOverlayIfNeeded(@NonNull WindowContainerTransaction wct,
2051                                                       @NonNull TaskContainer taskContainer) {
2052         // Dismiss always-on-top overlay container if it's the only container in the task and
2053         // there's no direct activity in the parent task.
2054         final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
2055         if (containers.size() != 1 || taskContainer.hasDirectActivity()) {
2056             return false;
2057         }
2058 
2059         final TaskFragmentContainer container = containers.getLast();
2060         if (!container.isAlwaysOnTopOverlay()) {
2061             return false;
2062         }
2063 
2064         mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependant */);
2065         return true;
2066     }
2067 
2068     /**
2069      * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
2070      * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
2071      * are {@code null}, the {@link SplitAttributes} will be calculated with
2072      * {@link SplitPresenter#computeSplitAttributes}.
2073      *
2074      * @param splitContainer  The {@link SplitContainer} to update
2075      * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
2076      *                        Otherwise, use the value calculated by
2077      *                        {@link SplitPresenter#computeSplitAttributes}
2078      * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
2079      */
2080     @VisibleForTesting
2081     @GuardedBy("mLock")
updateSplitContainerIfNeeded(@onNull SplitContainer splitContainer, @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes)2082     boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer,
2083             @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) {
2084         if (!isTopMostSplit(splitContainer)) {
2085             // Skip position update - it isn't the topmost split.
2086             return false;
2087         }
2088         if (splitContainer.getPrimaryContainer().isFinished()
2089                 || splitContainer.getSecondaryContainer().isFinished()) {
2090             // Skip position update - one or both containers are finished.
2091             return false;
2092         }
2093         if (splitAttributes == null) {
2094             final TaskContainer.TaskProperties taskProperties = splitContainer.getTaskContainer()
2095                     .getTaskProperties();
2096             final SplitRule splitRule = splitContainer.getSplitRule();
2097             final SplitAttributes defaultSplitAttributes = splitContainer
2098                     .getDefaultSplitAttributes();
2099             final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
2100             splitAttributes = mPresenter.computeSplitAttributes(taskProperties, splitRule,
2101                     defaultSplitAttributes, minDimensionsPair);
2102         }
2103         splitContainer.updateCurrentSplitAttributes(splitAttributes);
2104         if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
2105             // Placeholder was finished, the positions will be updated when its container is emptied
2106             return true;
2107         }
2108         mPresenter.updateSplitContainer(splitContainer, wct);
2109         return true;
2110     }
2111 
2112     /**
2113      * Whether the given split is the topmost split in the Task.
2114      */
isTopMostSplit(@onNull SplitContainer splitContainer)2115     private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
2116         final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
2117                 .getTaskContainer().getSplitContainers();
2118         return splitContainer == splitContainers.get(splitContainers.size() - 1);
2119     }
2120 
2121     /**
2122      * Returns the top active split container that has the provided container, if available.
2123      */
2124     @Nullable
getActiveSplitForContainer(@ullable TaskFragmentContainer container)2125     private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
2126         if (container == null) {
2127             return null;
2128         }
2129         return container.getTaskContainer().getActiveSplitForContainer(container);
2130     }
2131 
2132     /**
2133      * Returns the active split that has the provided containers as primary and secondary or as
2134      * secondary and primary, if available.
2135      */
2136     @GuardedBy("mLock")
2137     @Nullable
getActiveSplitForContainers( @onNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer)2138     SplitContainer getActiveSplitForContainers(
2139             @NonNull TaskFragmentContainer firstContainer,
2140             @NonNull TaskFragmentContainer secondContainer) {
2141         final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
2142                 .getSplitContainers();
2143         for (int i = splitContainers.size() - 1; i >= 0; i--) {
2144             final SplitContainer splitContainer = splitContainers.get(i);
2145             final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
2146             final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
2147             if ((firstContainer == secondary && secondContainer == primary)
2148                     || (firstContainer == primary && secondContainer == secondary)) {
2149                 return splitContainer;
2150             }
2151         }
2152         return null;
2153     }
2154 
2155     /**
2156      * Checks if the container requires a placeholder and launches it if necessary.
2157      */
2158     @GuardedBy("mLock")
launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)2159     private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
2160             @NonNull TaskFragmentContainer container) {
2161         final Activity topActivity = container.getTopNonFinishingActivity();
2162         if (topActivity == null) {
2163             return false;
2164         }
2165 
2166         return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
2167     }
2168 
2169     // Suppress GuardedBy warning because lint ask to mark this method as
2170     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
2171     @SuppressWarnings("GuardedBy")
2172     @GuardedBy("mLock")
launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnCreated)2173     boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
2174             @NonNull Activity activity, boolean isOnCreated) {
2175         if (activity.isFinishing()) {
2176             return false;
2177         }
2178 
2179         if (isAssociatedWithOverlay(activity)) {
2180             // Can't launch the placeholder if the activity associates an overlay.
2181             return false;
2182         }
2183 
2184         final TaskFragmentContainer container = getContainerWithActivity(activity);
2185         if (container != null && !allowLaunchPlaceholder(container)) {
2186             // We don't allow activity in this TaskFragment to launch placeholder.
2187             return false;
2188         }
2189 
2190         // Check if there is enough space for launch
2191         final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity);
2192 
2193         if (placeholderRule == null) {
2194             return false;
2195         }
2196 
2197         if (container != null && container.getTaskContainer().isPlaceholderRuleSuppressed()) {
2198             return false;
2199         }
2200 
2201         final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
2202         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
2203                 placeholderRule.getPlaceholderIntent());
2204         final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
2205                 placeholderRule, placeholderRule.getDefaultSplitAttributes(), minDimensionsPair);
2206         if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
2207             return false;
2208         }
2209 
2210         // TODO(b/190433398): Handle failed request
2211         final Bundle options = getPlaceholderOptions(activity, isOnCreated);
2212         startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
2213                 placeholderRule, splitAttributes, null /* failureCallback */,
2214                 true /* isPlaceholder */);
2215         return true;
2216     }
2217 
2218     /**
2219      * Whether or not to allow activity in this container to launch placeholder.
2220      */
2221     @GuardedBy("mLock")
allowLaunchPlaceholder(@onNull TaskFragmentContainer container)2222     private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
2223         if (container.isOverlay()) {
2224             // Don't launch placeholder if the container is an overlay.
2225             return false;
2226         }
2227 
2228         final TaskFragmentContainer topContainer = container.getTaskContainer()
2229                 .getTopNonFinishingTaskFragmentContainer();
2230         if (container != topContainer) {
2231             // The container is not the top most.
2232             if (!container.isVisible()) {
2233                 // In case the container is visible (the one on top may be transparent), we may
2234                 // still want to launch placeholder even if it is not the top most.
2235                 return false;
2236             }
2237             if (topContainer.isWaitingActivityAppear()) {
2238                 // When the top container appeared info is not sent by the server yet, the visible
2239                 // check above may not be reliable.
2240                 return false;
2241             }
2242         }
2243 
2244         final SplitContainer splitContainer = getActiveSplitForContainer(container);
2245         if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
2246             // Don't launch placeholder for primary split container.
2247             return false;
2248         }
2249         if (splitContainer instanceof SplitPinContainer) {
2250             // Don't launch placeholder if pinned
2251             return false;
2252         }
2253         return true;
2254     }
2255 
2256     /**
2257      * Gets the activity options for starting the placeholder activity. In case the placeholder is
2258      * launched when the Task is in the background, we don't want to bring the Task to the front.
2259      *
2260      * @param primaryActivity the primary activity to launch the placeholder from.
2261      * @param isOnCreated     whether this happens during the primary activity onCreated.
2262      */
2263     @VisibleForTesting
2264     @GuardedBy("mLock")
2265     @Nullable
getPlaceholderOptions(@onNull Activity primaryActivity, boolean isOnCreated)2266     Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
2267         // Setting avoid move to front will also skip the animation. We only want to do that when
2268         // the Task is currently in background.
2269         // Check if the primary is resumed or if this is called when the primary is onCreated
2270         // (not resumed yet).
2271         if (isOnCreated || primaryActivity.isResumed()) {
2272             // Only set trigger type if the launch happens in foreground.
2273             mTransactionManager.getCurrentTransactionRecord()
2274                     .setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
2275             return null;
2276         }
2277         final ActivityOptions options = ActivityOptions.makeBasic();
2278         options.setAvoidMoveToFront();
2279         return options.toBundle();
2280     }
2281 
2282     // Suppress GuardedBy warning because lint ask to mark this method as
2283     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
2284     @SuppressWarnings("GuardedBy")
2285     @VisibleForTesting
2286     @GuardedBy("mLock")
dismissPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull SplitContainer splitContainer)2287     boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
2288             @NonNull SplitContainer splitContainer) {
2289         if (!splitContainer.isPlaceholderContainer()) {
2290             return false;
2291         }
2292 
2293         if (isStickyPlaceholderRule(splitContainer.getSplitRule())) {
2294             // The placeholder should remain after it was first shown.
2295             return false;
2296         }
2297         final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
2298         if (SplitPresenter.shouldShowSplit(splitAttributes)) {
2299             return false;
2300         }
2301         if (SplitPresenter.shouldShowPlaceholderWhenExpanded(splitAttributes)) {
2302             return false;
2303         }
2304 
2305         mTransactionManager.getCurrentTransactionRecord()
2306                 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
2307         mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
2308                 false /* shouldFinishDependent */);
2309         return true;
2310     }
2311 
2312     /**
2313      * Returns the rule to launch a placeholder for the activity with the provided component name
2314      * if it is configured in the split config.
2315      */
2316     @GuardedBy("mLock")
getPlaceholderRule(@onNull Activity activity)2317     private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
2318         for (EmbeddingRule rule : mSplitRules) {
2319             if (!(rule instanceof SplitPlaceholderRule)) {
2320                 continue;
2321             }
2322             SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule;
2323             if (placeholderRule.matchesActivity(activity)) {
2324                 return placeholderRule;
2325             }
2326         }
2327         return null;
2328     }
2329 
2330     /**
2331      * Notifies listeners about changes to split states if necessary.
2332      */
2333     @VisibleForTesting
2334     @GuardedBy("mLock")
updateCallbackIfNecessary()2335     void updateCallbackIfNecessary() {
2336         updateSplitInfoCallbackIfNecessary();
2337         updateActivityStackCallbackIfNecessary();
2338     }
2339 
2340     /**
2341      * Notifies callbacks about changes to split states if necessary.
2342      */
2343     @GuardedBy("mLock")
updateSplitInfoCallbackIfNecessary()2344     private void updateSplitInfoCallbackIfNecessary() {
2345         if (!readyToReportToClient() || mSplitInfoCallback == null) {
2346             return;
2347         }
2348         final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
2349         if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
2350             return;
2351         }
2352         mLastReportedSplitStates.clear();
2353         mLastReportedSplitStates.addAll(currentSplitStates);
2354         mSplitInfoCallback.accept(currentSplitStates);
2355     }
2356 
2357     /**
2358      * Notifies callbacks about changes to {@link ActivityStack} states if necessary.
2359      */
2360     @GuardedBy("mLock")
updateActivityStackCallbackIfNecessary()2361     private void updateActivityStackCallbackIfNecessary() {
2362         if (!readyToReportToClient() || mActivityStackCallbacks.isEmpty()) {
2363             return;
2364         }
2365         final List<ActivityStack> currentActivityStacks = getActivityStacksIfStable();
2366         if (currentActivityStacks == null
2367                 || mLastReportedActivityStacks.equals(currentActivityStacks)) {
2368             return;
2369         }
2370         mLastReportedActivityStacks.clear();
2371         mLastReportedActivityStacks.addAll(currentActivityStacks);
2372         // Copy the map in case a callback is removed during the for-loop.
2373         final ArrayMap<Consumer<List<ActivityStack>>, Executor> callbacks =
2374                 new ArrayMap<>(mActivityStackCallbacks);
2375         for (int i = callbacks.size() - 1; i >= 0; --i) {
2376             final Executor executor = callbacks.valueAt(i);
2377             final Consumer<List<ActivityStack>> callback = callbacks.keyAt(i);
2378             executor.execute(() -> callback.accept(currentActivityStacks));
2379         }
2380     }
2381 
2382     /**
2383      * Returns a list of descriptors for currently active split states.
2384      *
2385      * @return a list of descriptors for currently active split states if all the containers are in
2386      * a stable state, or {@code null} otherwise.
2387      */
2388     @GuardedBy("mLock")
2389     @Nullable
getActiveSplitStatesIfStable()2390     private List<SplitInfo> getActiveSplitStatesIfStable() {
2391         final List<SplitInfo> splitStates = new ArrayList<>();
2392         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
2393             final List<SplitInfo> taskSplitStates =
2394                     mTaskContainers.valueAt(i).getSplitStatesIfStable();
2395             if (taskSplitStates == null) {
2396                 return null;
2397             }
2398             splitStates.addAll(taskSplitStates);
2399         }
2400         return splitStates;
2401     }
2402 
2403     /**
2404      * Returns a list of currently active {@link ActivityStack activityStacks}.
2405      *
2406      * @return a list of {@link ActivityStack activityStacks} if all the containers are in
2407      * a stable state, or {@code null} otherwise.
2408      */
2409     @GuardedBy("mLock")
2410     @Nullable
getActivityStacksIfStable()2411     private List<ActivityStack> getActivityStacksIfStable() {
2412         final List<ActivityStack> activityStacks = new ArrayList<>();
2413         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
2414             final List<ActivityStack> taskActivityStacks =
2415                     mTaskContainers.valueAt(i).getActivityStacksIfStable();
2416             if (taskActivityStacks == null) {
2417                 return null;
2418             }
2419             activityStacks.addAll(taskActivityStacks);
2420         }
2421         return activityStacks;
2422     }
2423 
2424     /**
2425      * Whether we can now report the split states to the client.
2426      */
2427     @GuardedBy("mLock")
readyToReportToClient()2428     private boolean readyToReportToClient() {
2429         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
2430             if (mTaskContainers.valueAt(i).isInIntermediateState()) {
2431                 // If any Task is in an intermediate state, wait for the server update.
2432                 return false;
2433             }
2434         }
2435         return true;
2436     }
2437 
2438     /**
2439      * Returns {@code true} if the container is expanded to occupy full task size.
2440      * Returns {@code false} if the container is included in an active split.
2441      */
shouldContainerBeExpanded(@ullable TaskFragmentContainer container)2442     boolean shouldContainerBeExpanded(@Nullable TaskFragmentContainer container) {
2443         if (container == null) {
2444             return false;
2445         }
2446         return getActiveSplitForContainer(container) == null;
2447     }
2448 
2449     /**
2450      * Returns a split rule for the provided pair of primary activity and secondary activity intent
2451      * if available.
2452      */
2453     @GuardedBy("mLock")
2454     @Nullable
getSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent)2455     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
2456             @NonNull Intent secondaryActivityIntent) {
2457         for (EmbeddingRule rule : mSplitRules) {
2458             if (!(rule instanceof SplitPairRule)) {
2459                 continue;
2460             }
2461             SplitPairRule pairRule = (SplitPairRule) rule;
2462             if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) {
2463                 return pairRule;
2464             }
2465         }
2466         return null;
2467     }
2468 
2469     /**
2470      * Returns a split rule for the provided pair of primary and secondary activities if available.
2471      */
2472     @GuardedBy("mLock")
2473     @Nullable
getSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)2474     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
2475             @NonNull Activity secondaryActivity) {
2476         for (EmbeddingRule rule : mSplitRules) {
2477             if (!(rule instanceof SplitPairRule)) {
2478                 continue;
2479             }
2480             SplitPairRule pairRule = (SplitPairRule) rule;
2481             final Intent intent = secondaryActivity.getIntent();
2482             if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity)
2483                     && (intent == null
2484                     || pairRule.matchesActivityIntentPair(primaryActivity, intent))) {
2485                 return pairRule;
2486             }
2487         }
2488         return null;
2489     }
2490 
2491     @Nullable
2492     @GuardedBy("mLock")
getContainer(@onNull IBinder fragmentToken)2493     TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
2494         return getContainer(container -> fragmentToken.equals(container.getTaskFragmentToken()));
2495     }
2496 
2497     @Nullable
2498     @GuardedBy("mLock")
getContainer(@onNull Predicate<TaskFragmentContainer> predicate)2499     TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
2500         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
2501             final TaskFragmentContainer container = mTaskContainers.valueAt(i)
2502                     .getContainer(predicate);
2503             if (container != null) {
2504                 return container;
2505             }
2506         }
2507         return null;
2508     }
2509 
2510     @VisibleForTesting
2511     @Nullable
2512     @GuardedBy("mLock")
getSplitContainer(@onNull IBinder token)2513     SplitContainer getSplitContainer(@NonNull IBinder token) {
2514         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
2515             final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers();
2516             for (SplitContainer container : containers) {
2517                 if (container.getToken().equals(token)) {
2518                     return container;
2519                 }
2520             }
2521         }
2522         return null;
2523     }
2524 
2525     @Nullable
2526     @GuardedBy("mLock")
getTaskContainer(int taskId)2527     TaskContainer getTaskContainer(int taskId) {
2528         return mTaskContainers.get(taskId);
2529     }
2530 
2531     @GuardedBy("mLock")
addTaskContainer(int taskId, TaskContainer taskContainer)2532     void addTaskContainer(int taskId, TaskContainer taskContainer) {
2533         mTaskContainers.put(taskId, taskContainer);
2534         mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor));
2535     }
2536 
getHandler()2537     Handler getHandler() {
2538         return mHandler;
2539     }
2540 
2541     @GuardedBy("mLock")
getTaskId(@onNull Activity activity)2542     int getTaskId(@NonNull Activity activity) {
2543         // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an
2544         // IPC call.
2545         final TaskFragmentContainer container = getContainerWithActivity(activity);
2546         return container != null ? container.getTaskId() : activity.getTaskId();
2547     }
2548 
2549     @Nullable
getActivity(@onNull IBinder activityToken)2550     Activity getActivity(@NonNull IBinder activityToken) {
2551         return ActivityThread.currentActivityThread().getActivity(activityToken);
2552     }
2553 
2554     @VisibleForTesting
2555     @Nullable
getActivityClientRecord(@onNull Activity activity)2556     ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
2557         return ActivityThread.currentActivityThread()
2558                 .getActivityClient(activity.getActivityToken());
2559     }
2560 
2561     @VisibleForTesting
getActivityStartMonitor()2562     ActivityStartMonitor getActivityStartMonitor() {
2563         return mActivityStartMonitor;
2564     }
2565 
2566     /**
2567      * Gets the token of the TaskFragment that embedded this activity. It is available as soon as
2568      * the activity is created and attached, so it can be used during {@link #onActivityCreated}
2569      * before the server notifies the organizer to avoid racing condition.
2570      */
2571     @VisibleForTesting
2572     @Nullable
getTaskFragmentTokenFromActivityClientRecord(@onNull Activity activity)2573     IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
2574         final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
2575         return record != null ? record.mTaskFragmentToken : null;
2576     }
2577 
2578     /**
2579      * Returns {@code true} if an Activity with the provided component name should always be
2580      * expanded to occupy full task bounds. Such activity must not be put in a split.
2581      */
2582     @VisibleForTesting
2583     @GuardedBy("mLock")
shouldExpand(@ullable Activity activity, @Nullable Intent intent)2584     boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
2585         for (EmbeddingRule rule : mSplitRules) {
2586             if (!(rule instanceof ActivityRule)) {
2587                 continue;
2588             }
2589             ActivityRule activityRule = (ActivityRule) rule;
2590             if (!activityRule.shouldAlwaysExpand()) {
2591                 continue;
2592             }
2593             if (activity != null && activityRule.matchesActivity(activity)) {
2594                 return true;
2595             } else if (intent != null && activityRule.matchesIntent(intent)) {
2596                 return true;
2597             }
2598         }
2599         return false;
2600     }
2601 
2602     /**
2603      * Checks whether the associated container should be destroyed together with a finishing
2604      * container. There is a case when primary containers for placeholders should be retained
2605      * despite the rule configuration to finish primary with secondary - if they are marked as
2606      * 'sticky' and the placeholder was finished when fully overlapping the primary container.
2607      *
2608      * @return {@code true} if the associated container should be retained (and not be finished).
2609      */
2610     // Suppress GuardedBy warning because lint ask to mark this method as
2611     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
2612     @SuppressWarnings("GuardedBy")
2613     @GuardedBy("mLock")
shouldRetainAssociatedContainer(@onNull TaskFragmentContainer finishingContainer, @NonNull TaskFragmentContainer associatedContainer)2614     boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
2615             @NonNull TaskFragmentContainer associatedContainer) {
2616         SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
2617                 finishingContainer);
2618         if (splitContainer == null) {
2619             // Containers are not in the same split, no need to retain.
2620             return false;
2621         }
2622         // Find the finish behavior for the associated container
2623         int finishBehavior;
2624         SplitRule splitRule = splitContainer.getSplitRule();
2625         if (finishingContainer == splitContainer.getPrimaryContainer()) {
2626             finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule);
2627         } else {
2628             finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule);
2629         }
2630         // Decide whether the associated container should be retained based on the current
2631         // presentation mode.
2632         if (shouldShowSplit(splitContainer)) {
2633             return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
2634         } else {
2635             return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
2636         }
2637     }
2638 
2639     /**
2640      * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer)
2641      */
2642     @GuardedBy("mLock")
shouldRetainAssociatedActivity(@onNull TaskFragmentContainer finishingContainer, @NonNull Activity associatedActivity)2643     boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer,
2644             @NonNull Activity associatedActivity) {
2645         final TaskFragmentContainer associatedContainer = getContainerWithActivity(
2646                 associatedActivity);
2647         if (associatedContainer == null) {
2648             return false;
2649         }
2650 
2651         return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
2652     }
2653 
2654     /**
2655      * Gets all overlay containers from all tasks in this process, or an empty list if there's
2656      * no overlay container.
2657      */
2658     @VisibleForTesting
2659     @GuardedBy("mLock")
2660     @NonNull
getAllNonFinishingOverlayContainers()2661     List<TaskFragmentContainer> getAllNonFinishingOverlayContainers() {
2662         final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
2663         for (int i = 0; i < mTaskContainers.size(); i++) {
2664             final TaskContainer taskContainer = mTaskContainers.valueAt(i);
2665             final List<TaskFragmentContainer> overlayContainersPerTask = taskContainer
2666                     .getTaskFragmentContainers()
2667                     .stream()
2668                     .filter(c -> c.isOverlay() && !c.isFinished())
2669                     .toList();
2670             overlayContainers.addAll(overlayContainersPerTask);
2671         }
2672         return overlayContainers;
2673     }
2674 
2675     @GuardedBy("mLock")
isAssociatedWithOverlay(@onNull Activity activity)2676     private boolean isAssociatedWithOverlay(@NonNull Activity activity) {
2677         final TaskContainer taskContainer = getTaskContainer(getTaskId(activity));
2678         if (taskContainer == null) {
2679             return false;
2680         }
2681         return taskContainer.getContainer(c -> c.isOverlay() && !c.isFinished()
2682                 && c.getAssociatedActivityToken() == activity.getActivityToken()) != null;
2683     }
2684 
2685     /**
2686      * Creates an overlay container or updates a visible overlay container if its
2687      * {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()}
2688      * and {@link TaskFragmentContainer#getAssociatedActivityToken()} matches.
2689      * <p>
2690      * This method will also dismiss any existing overlay container if:
2691      * <ul>
2692      *   <li>it's visible but not meet the criteria to update overlay</li>
2693      *   <li>{@link TaskFragmentContainer#getOverlayTag()} matches but not meet the criteria to
2694      *   update overlay</li>
2695      * </ul>
2696      *
2697      * @param wct the {@link WindowContainerTransaction}
2698      * @param options the {@link ActivityOptions} to launch the overlay
2699      * @param intent the intent of activity to launch
2700      * @param launchActivity the activity to launch the overlay container
2701      * @return the overlay container
2702      */
2703     @VisibleForTesting
2704     // Suppress GuardedBy warning because lint ask to mark this method as
2705     // @GuardedBy(container.mController.mLock), which is mLock itself
2706     @SuppressWarnings("GuardedBy")
2707     @GuardedBy("mLock")
2708     @Nullable
createOrUpdateOverlayTaskFragmentIfNeeded( @onNull WindowContainerTransaction wct, @NonNull Bundle options, @NonNull Intent intent, @NonNull Activity launchActivity)2709     TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
2710             @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
2711             @NonNull Intent intent, @NonNull Activity launchActivity) {
2712         if (isActivityFromSplit(launchActivity)) {
2713             // We restrict to launch the overlay from split. Fallback to treat it as normal
2714             // launch.
2715             return null;
2716         }
2717 
2718         final List<TaskFragmentContainer> overlayContainers =
2719                 getAllNonFinishingOverlayContainers();
2720         final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
2721         final boolean associateLaunchingActivity = options
2722                 .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true);
2723 
2724         // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
2725         // specified by Intent, expand the overlay container to fill the parent task instead.
2726         final ActivityStackAttributesCalculatorParams params =
2727                 new ActivityStackAttributesCalculatorParams(
2728                         mPresenter.createParentContainerInfoFromTaskProperties(
2729                                 mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
2730         // Fallback to expand the bounds if there's no activityStackAttributes calculator.
2731         final ActivityStackAttributes attrs;
2732         if (mActivityStackAttributesCalculator != null) {
2733             attrs = mActivityStackAttributesCalculator.apply(params);
2734         } else {
2735             attrs = new ActivityStackAttributes.Builder().build();
2736             Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay "
2737                     + "container as expected.");
2738         }
2739 
2740         final int taskId = getTaskId(launchActivity);
2741         if (!overlayContainers.isEmpty()) {
2742             for (final TaskFragmentContainer overlayContainer : overlayContainers) {
2743                 final boolean isTopNonFinishingOverlay = overlayContainer.equals(
2744                         overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer(
2745                                 true /* includePin */, true /* includeOverlay */));
2746                 if (taskId != overlayContainer.getTaskId()) {
2747                     // If there's an overlay container with same tag in a different task,
2748                     // dismiss the overlay container since the tag must be unique per process.
2749                     if (overlayTag.equals(overlayContainer.getOverlayTag())) {
2750                         Log.w(TAG, "The overlay container with tag:"
2751                                 + overlayContainer.getOverlayTag() + " is dismissed because"
2752                                 + " there's an existing overlay container with the same tag but"
2753                                 + " different task ID:" + overlayContainer.getTaskId() + ". "
2754                                 + "The new associated activity is " + launchActivity);
2755                         mPresenter.cleanupContainer(wct, overlayContainer,
2756                                 false /* shouldFinishDependant */);
2757                     }
2758                     continue;
2759                 }
2760                 if (!overlayTag.equals(overlayContainer.getOverlayTag())) {
2761                     // If there's an overlay container with different tag on top in the same
2762                     // task, dismiss the existing overlay container.
2763                     if (isTopNonFinishingOverlay) {
2764                         mPresenter.cleanupContainer(wct, overlayContainer,
2765                                 false /* shouldFinishDependant */);
2766                     }
2767                     continue;
2768                 }
2769                 // The overlay container has the same tag and task ID with the new launching
2770                 // overlay container.
2771                 if (!isTopNonFinishingOverlay) {
2772                     // Dismiss the invisible overlay container regardless of activity
2773                     // association if it collides the tag of new launched overlay container .
2774                     Log.w(TAG, "The invisible overlay container with tag:"
2775                             + overlayContainer.getOverlayTag() + " is dismissed because"
2776                             + " there's a launching overlay container with the same tag."
2777                             + " The new associated activity is " + launchActivity);
2778                     mPresenter.cleanupContainer(wct, overlayContainer,
2779                             false /* shouldFinishDependant */);
2780                     continue;
2781                 }
2782                 // Requesting an always-on-top overlay.
2783                 if (!associateLaunchingActivity) {
2784                     if (overlayContainer.isOverlayWithActivityAssociation()) {
2785                         // Dismiss the overlay container since it has associated with an activity.
2786                         Log.w(TAG, "The overlay container with tag:"
2787                                 + overlayContainer.getOverlayTag() + " is dismissed because"
2788                                 + " there's an existing overlay container with the same tag but"
2789                                 + " different associated launching activity. The overlay container"
2790                                 + " doesn't associate with any activity.");
2791                         mPresenter.cleanupContainer(wct, overlayContainer,
2792                                 false /* shouldFinishDependant */);
2793                         continue;
2794                     } else {
2795                         // The existing overlay container doesn't associate an activity as well.
2796                         // Just update the overlay and return.
2797                         // Note that going to this condition means the tag, task ID matches a
2798                         // visible always-on-top overlay, and won't dismiss any overlay any more.
2799                         mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
2800                                 getMinDimensions(intent));
2801                         return overlayContainer;
2802                     }
2803                 }
2804                 if (launchActivity.getActivityToken()
2805                         != overlayContainer.getAssociatedActivityToken()) {
2806                     Log.w(TAG, "The overlay container with tag:"
2807                             + overlayContainer.getOverlayTag() + " is dismissed because"
2808                             + " there's an existing overlay container with the same tag but"
2809                             + " different associated launching activity. The new associated"
2810                             + " activity is " + launchActivity);
2811                     // The associated activity must be the same, or it will be dismissed.
2812                     mPresenter.cleanupContainer(wct, overlayContainer,
2813                             false /* shouldFinishDependant */);
2814                     continue;
2815                 }
2816                 // Reaching here means the launching activity launch an overlay container with the
2817                 // same task ID, tag, while there's a previously launching visible overlay
2818                 // container. We'll regard it as updating the existing overlay container.
2819                 mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
2820                         getMinDimensions(intent));
2821                 return overlayContainer;
2822 
2823             }
2824         }
2825         // Launch the overlay container to the task with taskId.
2826         return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
2827                 options, associateLaunchingActivity);
2828     }
2829 
2830     @GuardedBy("mLock")
isActivityFromSplit(@onNull Activity activity)2831     private boolean isActivityFromSplit(@NonNull Activity activity) {
2832         final TaskFragmentContainer container = getContainerWithActivity(activity);
2833         if (container == null) {
2834             return false;
2835         }
2836         return getActiveSplitForContainer(container) != null;
2837     }
2838 
2839     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
2840 
2841         @Override
onActivityPreCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)2842         public void onActivityPreCreated(@NonNull Activity activity,
2843                 @Nullable Bundle savedInstanceState) {
2844             if (activity.isChild()) {
2845                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
2846                 // window will just be a child of the parent Activity window.
2847                 return;
2848             }
2849             synchronized (mLock) {
2850                 final IBinder activityToken = activity.getActivityToken();
2851                 final IBinder initialTaskFragmentToken =
2852                         getTaskFragmentTokenFromActivityClientRecord(activity);
2853                 // If the activity is not embedded, then it will not have an initial task fragment
2854                 // token so no further action is needed.
2855                 if (initialTaskFragmentToken == null) {
2856                     return;
2857                 }
2858                 for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
2859                     final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
2860                             .getTaskFragmentContainers();
2861                     for (int j = containers.size() - 1; j >= 0; j--) {
2862                         final TaskFragmentContainer container = containers.get(j);
2863                         if (!container.hasActivity(activityToken)
2864                                 && container.getTaskFragmentToken()
2865                                 .equals(initialTaskFragmentToken)) {
2866                             if (ActivityClient.getInstance().isRequestedToLaunchInTaskFragment(
2867                                     activityToken, initialTaskFragmentToken)) {
2868                                 container.addPendingAppearedInRequestedTaskFragmentActivity(
2869                                         activity);
2870                             }
2871 
2872                             // The onTaskFragmentInfoChanged callback containing this activity has
2873                             // not reached the client yet, so add the activity to the pending
2874                             // appeared activities.
2875                             container.addPendingAppearedActivity(activity);
2876                             return;
2877                         }
2878                     }
2879                 }
2880             }
2881         }
2882 
2883         @Override
onActivityPostCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)2884         public void onActivityPostCreated(@NonNull Activity activity,
2885                 @Nullable Bundle savedInstanceState) {
2886             if (activity.isChild()) {
2887                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
2888                 // window will just be a child of the parent Activity window.
2889                 return;
2890             }
2891             // Calling after Activity#onCreate is complete to allow the app launch something
2892             // first. In case of a configured placeholder activity we want to make sure
2893             // that we don't launch it if an activity itself already requested something to be
2894             // launched to side.
2895             synchronized (mLock) {
2896                 final TransactionRecord transactionRecord = mTransactionManager
2897                         .startNewTransaction();
2898                 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
2899                 SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
2900                         activity);
2901                 // The WCT should be applied and merged to the activity launch transition.
2902                 transactionRecord.apply(false /* shouldApplyIndependently */);
2903             }
2904         }
2905 
2906         @Override
onActivityConfigurationChanged(@onNull Activity activity)2907         public void onActivityConfigurationChanged(@NonNull Activity activity) {
2908             if (activity.isChild()) {
2909                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
2910                 // window will just be a child of the parent Activity window.
2911                 return;
2912             }
2913             synchronized (mLock) {
2914                 final TransactionRecord transactionRecord = mTransactionManager
2915                         .startNewTransaction();
2916                 SplitController.this.onActivityConfigurationChanged(
2917                         transactionRecord.getTransaction(), activity);
2918                 // The WCT should be applied and merged to the Task change transition so that the
2919                 // placeholder is launched in the same transition.
2920                 transactionRecord.apply(false /* shouldApplyIndependently */);
2921             }
2922         }
2923 
2924         @Override
onActivityPostPaused(@onNull Activity activity)2925         public void onActivityPostPaused(@NonNull Activity activity) {
2926             if (activity.isChild()) {
2927                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
2928                 // window will just be a child of the parent Activity window.
2929                 return;
2930             }
2931             synchronized (mLock) {
2932                 final TransactionRecord transactionRecord = mTransactionManager
2933                         .startNewTransaction();
2934                 transactionRecord.setOriginType(TRANSIT_CLOSE);
2935                 SplitController.this.onActivityPaused(
2936                         transactionRecord.getTransaction(), activity);
2937                 transactionRecord.apply(false /* shouldApplyIndependently */);
2938             }
2939         }
2940 
2941         @Override
onActivityPostDestroyed(@onNull Activity activity)2942         public void onActivityPostDestroyed(@NonNull Activity activity) {
2943             if (activity.isChild()) {
2944                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
2945                 // window will just be a child of the parent Activity window.
2946                 return;
2947             }
2948             synchronized (mLock) {
2949                 final TransactionRecord transactionRecord = mTransactionManager
2950                         .startNewTransaction();
2951                 transactionRecord.setOriginType(TRANSIT_CLOSE);
2952                 SplitController.this.onActivityDestroyed(
2953                         transactionRecord.getTransaction(), activity);
2954                 transactionRecord.apply(false /* shouldApplyIndependently */);
2955             }
2956         }
2957     }
2958 
2959     /**
2960      * Executor that posts on the main application thread.
2961      */
2962     private static class MainThreadExecutor implements Executor {
2963         private final Handler mHandler = new Handler(Looper.getMainLooper());
2964 
2965         @Override
execute(@onNull Runnable r)2966         public void execute(@NonNull Runnable r) {
2967             mHandler.post(r);
2968         }
2969     }
2970 
2971     /**
2972      * A monitor that intercepts all activity start requests originating in the client process and
2973      * can amend them to target a specific task fragment to form a split.
2974      */
2975     @VisibleForTesting
2976     class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
2977         @VisibleForTesting
2978         @GuardedBy("mLock")
2979         Intent mCurrentIntent;
2980 
2981         @Override
onStartActivity(@onNull Context who, @NonNull Intent intent, @NonNull Bundle options)2982         public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
2983                 @NonNull Intent intent, @NonNull Bundle options) {
2984             // TODO(b/232042367): Consolidate the activity create handling so that we can handle
2985             // cross-process the same as normal.
2986 
2987             final Bundle bundle = options.getBundle(KEY_ACTIVITY_STACK_TOKEN);
2988             if (bundle != null) {
2989                 final IBinder activityStackToken = ActivityStack.Token.readFromBundle(bundle)
2990                         .getRawToken();
2991                 // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity
2992                 // into the taskFragment associated with the token.
2993                 options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken);
2994             }
2995 
2996             // Early return if the launching taskfragment is already been set.
2997             // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to
2998             // bundle. This is still needed to support #setLaunchingActivityStack.
2999             if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
3000                 synchronized (mLock) {
3001                     mCurrentIntent = intent;
3002                 }
3003                 return super.onStartActivity(who, intent, options);
3004             }
3005 
3006             final Activity launchingActivity;
3007             if (who instanceof Activity) {
3008                 // We will check if the new activity should be split with the activity that launched
3009                 // it.
3010                 final Activity activity = (Activity) who;
3011                 // For Activity that is child of another Activity (ActivityGroup), treat the parent
3012                 // Activity as the launching one because it's window will just be a child of the
3013                 // parent Activity window.
3014                 launchingActivity = activity.isChild() ? activity.getParent() : activity;
3015                 if (isInPictureInPicture(launchingActivity)) {
3016                     // We don't embed activity when it is in PIP.
3017                     return super.onStartActivity(who, intent, options);
3018                 }
3019             } else {
3020                 // When the context to start activity is not an Activity context, we will check if
3021                 // the new activity should be embedded in the known Task belonging to the organizer
3022                 // process. @see #resolveStartActivityIntentFromNonActivityContext
3023                 // It is a current security limitation that we can't access the activity info of
3024                 // other process even if it is in the same Task.
3025                 launchingActivity = null;
3026             }
3027 
3028             synchronized (mLock) {
3029                 final TransactionRecord transactionRecord = mTransactionManager
3030                         .startNewTransaction();
3031                 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
3032                 final WindowContainerTransaction wct = transactionRecord.getTransaction();
3033                 final TaskFragmentContainer launchedInTaskFragment;
3034                 if (launchingActivity != null) {
3035                     final int taskId = getTaskId(launchingActivity);
3036                     final String overlayTag = options.getString(KEY_OVERLAY_TAG);
3037                     if (Flags.activityEmbeddingOverlayPresentationFlag()
3038                             && overlayTag != null) {
3039                         launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
3040                                 options, intent, launchingActivity);
3041                     } else {
3042                         launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
3043                                 launchingActivity);
3044                     }
3045                 } else {
3046                     launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
3047                             intent);
3048                 }
3049                 if (launchedInTaskFragment != null) {
3050                     // Make sure the WCT is applied immediately instead of being queued so that the
3051                     // TaskFragment will be ready before activity attachment.
3052                     transactionRecord.apply(false /* shouldApplyIndependently */);
3053                     // Amend the request to let the WM know that the activity should be placed in
3054                     // the dedicated container.
3055                     // TODO(b/229680885): skip override launching TaskFragment token by split-rule
3056                     options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
3057                             launchedInTaskFragment.getTaskFragmentToken());
3058                     mCurrentIntent = intent;
3059                 } else {
3060                     transactionRecord.abort();
3061                 }
3062             }
3063 
3064             return super.onStartActivity(who, intent, options);
3065         }
3066 
3067         @Override
onStartActivityResult(int result, @NonNull Bundle bOptions)3068         public void onStartActivityResult(int result, @NonNull Bundle bOptions) {
3069             super.onStartActivityResult(result, bOptions);
3070             synchronized (mLock) {
3071                 if (mCurrentIntent != null && result != START_SUCCESS) {
3072                     // Clear the pending appeared intent if the activity was not started
3073                     // successfully.
3074                     final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
3075                     if (token != null) {
3076                         final TaskFragmentContainer container = getContainer(token);
3077                         if (container != null) {
3078                             container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
3079                         }
3080                     }
3081                 }
3082                 mCurrentIntent = null;
3083             }
3084         }
3085     }
3086 
3087     /**
3088      * Checks if an activity is embedded and its presentation is customized by a
3089      * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
3090      */
3091     @Override
isActivityEmbedded(@onNull Activity activity)3092     public boolean isActivityEmbedded(@NonNull Activity activity) {
3093         Objects.requireNonNull(activity);
3094         synchronized (mLock) {
3095             if (Flags.activityWindowInfoFlag()) {
3096                 final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
3097                 return activityWindowInfo != null && activityWindowInfo.isEmbedded();
3098             }
3099             return mPresenter.isActivityEmbedded(activity.getActivityToken());
3100         }
3101     }
3102 
3103     @Override
setEmbeddedActivityWindowInfoCallback(@onNull Executor executor, @NonNull Consumer<EmbeddedActivityWindowInfo> callback)3104     public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
3105             @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
3106         if (!Flags.activityWindowInfoFlag()) {
3107             return;
3108         }
3109         Objects.requireNonNull(executor);
3110         Objects.requireNonNull(callback);
3111         synchronized (mLock) {
3112             if (mEmbeddedActivityWindowInfoCallback == null) {
3113                 ClientTransactionListenerController.getInstance()
3114                         .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
3115             }
3116             mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
3117         }
3118     }
3119 
3120     @Override
clearEmbeddedActivityWindowInfoCallback()3121     public void clearEmbeddedActivityWindowInfoCallback() {
3122         if (!Flags.activityWindowInfoFlag()) {
3123             return;
3124         }
3125         synchronized (mLock) {
3126             if (mEmbeddedActivityWindowInfoCallback == null) {
3127                 return;
3128             }
3129             mEmbeddedActivityWindowInfoCallback = null;
3130             ClientTransactionListenerController.getInstance()
3131                     .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
3132         }
3133     }
3134 
3135     @VisibleForTesting
3136     @GuardedBy("mLock")
3137     @Nullable
getActivityWindowInfoListener()3138     BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
3139         return mActivityWindowInfoListener;
3140     }
3141 
3142     @Nullable
3143     @Override
getEmbeddedActivityWindowInfo(@onNull Activity activity)3144     public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
3145         if (!Flags.activityWindowInfoFlag()) {
3146             return null;
3147         }
3148         synchronized (mLock) {
3149             final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
3150             return activityWindowInfo != null
3151                     ? translateActivityWindowInfo(activity, activityWindowInfo)
3152                     : null;
3153         }
3154     }
3155 
3156     @VisibleForTesting
onActivityWindowInfoChanged(@onNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo)3157     void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
3158             @NonNull ActivityWindowInfo activityWindowInfo) {
3159         synchronized (mLock) {
3160             if (mEmbeddedActivityWindowInfoCallback == null) {
3161                 return;
3162             }
3163             final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
3164             final Consumer<EmbeddedActivityWindowInfo> callback =
3165                     mEmbeddedActivityWindowInfoCallback.second;
3166 
3167             final Activity activity = getActivity(activityToken);
3168             if (activity == null) {
3169                 return;
3170             }
3171             final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
3172                     activity, activityWindowInfo);
3173 
3174             executor.execute(() -> callback.accept(info));
3175         }
3176     }
3177 
3178     @Nullable
getActivityWindowInfo(@onNull Activity activity)3179     private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
3180         if (activity.isFinishing()) {
3181             return null;
3182         }
3183         final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
3184         return record != null ? record.getActivityWindowInfo() : null;
3185     }
3186 
3187     @NonNull
translateActivityWindowInfo( @onNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo)3188     private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
3189             @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
3190         final boolean isEmbedded = activityWindowInfo.isEmbedded();
3191         final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
3192         final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
3193         return new EmbeddedActivityWindowInfo(activity, isEmbedded, taskBounds,
3194                 activityStackBounds);
3195     }
3196 
3197     /**
3198      * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
3199      * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
3200      * {@link SplitContainer} if there is any.
3201      */
canReuseContainer(@onNull SplitRule rule1, @NonNull SplitRule rule2, @NonNull WindowMetrics parentWindowMetrics, @NonNull SplitAttributes calculatedSplitAttributes, @NonNull SplitAttributes containerSplitAttributes)3202     private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
3203             @NonNull WindowMetrics parentWindowMetrics,
3204             @NonNull SplitAttributes calculatedSplitAttributes,
3205             @NonNull SplitAttributes containerSplitAttributes) {
3206         if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
3207             return false;
3208         }
3209         return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
3210                 parentWindowMetrics)
3211                 // Besides rules, we should also check whether the SplitContainer's splitAttributes
3212                 // matches the current splitAttributes or not. The splitAttributes may change
3213                 // if the app chooses different SplitAttributes calculator function before a new
3214                 // activity is started even they match the same splitRule.
3215                 && calculatedSplitAttributes.equals(containerSplitAttributes);
3216     }
3217 
3218     /**
3219      * Whether the two rules have the same presentation.
3220      */
3221     @VisibleForTesting
areRulesSamePresentation(@onNull SplitPairRule rule1, @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics)3222     static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
3223             @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
3224         if (rule1.getTag() != null || rule2.getTag() != null) {
3225             // Tag must be unique if it is set. We don't want to reuse the container if the rules
3226             // have different tags because they can have different SplitAttributes later through
3227             // SplitAttributesCalculator.
3228             return Objects.equals(rule1.getTag(), rule2.getTag());
3229         }
3230         // If both rules don't have tag, compare all SplitRules' properties that may affect their
3231         // SplitAttributes.
3232         // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
3233         return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
3234                 && rule1.checkParentMetrics(parentWindowMetrics)
3235                 == rule2.checkParentMetrics(parentWindowMetrics)
3236                 && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
3237                 && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
3238     }
3239 
3240     /**
3241      * Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given
3242      * rule.
3243      */
isContainerReusableRule(@onNull SplitRule rule)3244     private static boolean isContainerReusableRule(@NonNull SplitRule rule) {
3245         // We don't expect to reuse the placeholder rule.
3246         if (!(rule instanceof SplitPairRule)) {
3247             return false;
3248         }
3249         final SplitPairRule pairRule = (SplitPairRule) rule;
3250 
3251         // Not reuse if it needs to destroy the existing.
3252         return !pairRule.shouldClearTop();
3253     }
3254 
isInPictureInPicture(@onNull Activity activity)3255     private static boolean isInPictureInPicture(@NonNull Activity activity) {
3256         return isInPictureInPicture(activity.getResources().getConfiguration());
3257     }
3258 
isInPictureInPicture(@onNull TaskFragmentContainer tf)3259     private static boolean isInPictureInPicture(@NonNull TaskFragmentContainer tf) {
3260         return isInPictureInPicture(tf.getInfo().getConfiguration());
3261     }
3262 
isInPictureInPicture(@ullable Configuration configuration)3263     private static boolean isInPictureInPicture(@Nullable Configuration configuration) {
3264         return configuration != null
3265                 && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
3266     }
3267 
3268     @GuardedBy("mLock")
updateDivider( @onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)3269     void updateDivider(
3270             @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
3271         final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
3272         final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
3273         dividerPresenter.updateDivider(
3274                 wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
3275     }
3276 
3277     @Override
onStartDragging(@onNull Consumer<WindowContainerTransaction> action)3278     public void onStartDragging(@NonNull Consumer<WindowContainerTransaction> action) {
3279         synchronized (mLock) {
3280             final TransactionRecord transactionRecord =
3281                     mTransactionManager.startNewTransaction();
3282             final WindowContainerTransaction wct = transactionRecord.getTransaction();
3283             action.accept(wct);
3284             transactionRecord.apply(false /* shouldApplyIndependently */);
3285         }
3286     }
3287 
3288     @Override
onFinishDragging( int taskId, @NonNull Consumer<WindowContainerTransaction> action)3289     public void onFinishDragging(
3290             int taskId,
3291             @NonNull Consumer<WindowContainerTransaction> action) {
3292         synchronized (mLock) {
3293             final TransactionRecord transactionRecord =
3294                     mTransactionManager.startNewTransaction();
3295             transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_DRAG_RESIZE);
3296             final WindowContainerTransaction wct = transactionRecord.getTransaction();
3297             final TaskContainer taskContainer = mTaskContainers.get(taskId);
3298             if (taskContainer != null) {
3299                 final DividerPresenter dividerPresenter =
3300                         mDividerPresenters.get(taskContainer.getTaskId());
3301                 final List<TaskFragmentContainer> containersToFinish = new ArrayList<>();
3302                 taskContainer.updateTopSplitContainerForDivider(
3303                         dividerPresenter, containersToFinish);
3304                 for (final TaskFragmentContainer container : containersToFinish) {
3305                     mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
3306                 }
3307                 updateContainersInTask(wct, taskContainer);
3308             }
3309             action.accept(wct);
3310             transactionRecord.apply(false /* shouldApplyIndependently */);
3311         }
3312     }
3313 }
3314