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