• 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.WindowConfiguration.WINDOWING_MODE_PINNED;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
22 import static android.view.Display.DEFAULT_DISPLAY;
23 import static android.view.WindowManager.TRANSIT_CLOSE;
24 import static android.view.WindowManager.TRANSIT_OPEN;
25 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
26 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
27 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
28 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
29 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
30 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
31 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
32 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
33 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
34 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
35 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
36 
37 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
38 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
39 import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
40 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
41 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
42 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
43 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
44 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
45 
46 import android.app.Activity;
47 import android.app.ActivityClient;
48 import android.app.ActivityOptions;
49 import android.app.ActivityThread;
50 import android.app.Application;
51 import android.app.Instrumentation;
52 import android.content.ComponentName;
53 import android.content.Context;
54 import android.content.Intent;
55 import android.content.res.Configuration;
56 import android.graphics.Rect;
57 import android.os.Bundle;
58 import android.os.Handler;
59 import android.os.IBinder;
60 import android.os.Looper;
61 import android.os.SystemProperties;
62 import android.util.ArraySet;
63 import android.util.Log;
64 import android.util.Pair;
65 import android.util.Size;
66 import android.util.SparseArray;
67 import android.view.WindowMetrics;
68 import android.window.TaskFragmentAnimationParams;
69 import android.window.TaskFragmentInfo;
70 import android.window.TaskFragmentParentInfo;
71 import android.window.TaskFragmentTransaction;
72 import android.window.WindowContainerTransaction;
73 
74 import androidx.annotation.GuardedBy;
75 import androidx.annotation.NonNull;
76 import androidx.annotation.Nullable;
77 import androidx.window.common.CommonFoldingFeature;
78 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
79 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
80 import androidx.window.extensions.WindowExtensionsImpl;
81 import androidx.window.extensions.core.util.function.Consumer;
82 import androidx.window.extensions.core.util.function.Function;
83 import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
84 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
85 
86 import com.android.internal.annotations.VisibleForTesting;
87 
88 import java.util.ArrayList;
89 import java.util.List;
90 import java.util.Objects;
91 import java.util.Set;
92 import java.util.concurrent.Executor;
93 
94 /**
95  * Main controller class that manages split states and presentation.
96  */
97 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
98         ActivityEmbeddingComponent {
99     static final String TAG = "SplitController";
100     static final boolean ENABLE_SHELL_TRANSITIONS =
101             SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
102 
103     @VisibleForTesting
104     @GuardedBy("mLock")
105     final SplitPresenter mPresenter;
106 
107     @VisibleForTesting
108     @GuardedBy("mLock")
109     final TransactionManager mTransactionManager;
110 
111     // Currently applied split configuration.
112     @GuardedBy("mLock")
113     private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
114 
115     /**
116      * A developer-defined {@link SplitAttributes} calculator to compute the current
117      * {@link SplitAttributes} with the current device and window states.
118      * It is registered via {@link #setSplitAttributesCalculator(Function)}
119      * and unregistered via {@link #clearSplitAttributesCalculator()}.
120      * This is called when:
121      * <ul>
122      *   <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
123      *     WindowContainerTransaction)}</li>
124      *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
125      *   <li>Checking whether the place holder should be launched if there's a Activity matches
126      *   {@link SplitPlaceholderRule} </li>
127      * </ul>
128      */
129     @GuardedBy("mLock")
130     @Nullable
131     private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
132 
133     /**
134      * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
135      * below it.
136      * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
137      * organizer.
138      */
139     @VisibleForTesting
140     @GuardedBy("mLock")
141     final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
142 
143     /** Callback to Jetpack to notify about changes to split states. */
144     @GuardedBy("mLock")
145     @Nullable
146     private Consumer<List<SplitInfo>> mEmbeddingCallback;
147     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
148     private final Handler mHandler;
149     final Object mLock = new Object();
150     private final ActivityStartMonitor mActivityStartMonitor;
151 
SplitController(@onNull WindowLayoutComponentImpl windowLayoutComponent, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer)152     public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
153             @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
154         final MainThreadExecutor executor = new MainThreadExecutor();
155         mHandler = executor.mHandler;
156         mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
157         mTransactionManager = new TransactionManager(mPresenter);
158         final ActivityThread activityThread = ActivityThread.currentActivityThread();
159         final Application application = activityThread.getApplication();
160         // Register a callback to be notified about activities being created.
161         application.registerActivityLifecycleCallbacks(new LifecycleCallbacks());
162         // Intercept activity starts to route activities to new containers if necessary.
163         Instrumentation instrumentation = activityThread.getInstrumentation();
164 
165         mActivityStartMonitor = new ActivityStartMonitor();
166         instrumentation.addMonitor(mActivityStartMonitor);
167         foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener());
168     }
169 
170     private class FoldingFeatureListener
171             implements java.util.function.Consumer<List<CommonFoldingFeature>> {
172         @Override
accept(List<CommonFoldingFeature> foldingFeatures)173         public void accept(List<CommonFoldingFeature> foldingFeatures) {
174             synchronized (mLock) {
175                 final TransactionRecord transactionRecord = mTransactionManager
176                         .startNewTransaction();
177                 final WindowContainerTransaction wct = transactionRecord.getTransaction();
178                 for (int i = 0; i < mTaskContainers.size(); i++) {
179                     final TaskContainer taskContainer = mTaskContainers.valueAt(i);
180                     if (!taskContainer.isVisible()) {
181                         continue;
182                     }
183                     if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) {
184                         continue;
185                     }
186                     // TODO(b/238948678): Support reporting display features in all windowing modes.
187                     if (taskContainer.isInMultiWindow()) {
188                         continue;
189                     }
190                     if (taskContainer.isEmpty()) {
191                         continue;
192                     }
193                     updateContainersInTask(wct, taskContainer);
194                 }
195                 // The WCT should be applied and merged to the device state change transition if
196                 // there is one.
197                 transactionRecord.apply(false /* shouldApplyIndependently */);
198             }
199         }
200     }
201 
202     /** Updates the embedding rules applied to future activity launches. */
203     @Override
setEmbeddingRules(@onNull Set<EmbeddingRule> rules)204     public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
205         synchronized (mLock) {
206             mSplitRules.clear();
207             mSplitRules.addAll(rules);
208         }
209     }
210 
211     @Override
setSplitAttributesCalculator( @onNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator)212     public void setSplitAttributesCalculator(
213             @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
214         synchronized (mLock) {
215             mSplitAttributesCalculator = calculator;
216         }
217     }
218 
219     @Override
clearSplitAttributesCalculator()220     public void clearSplitAttributesCalculator() {
221         synchronized (mLock) {
222             mSplitAttributesCalculator = null;
223         }
224     }
225 
226     @GuardedBy("mLock")
227     @Nullable
getSplitAttributesCalculator()228     Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
229         return mSplitAttributesCalculator;
230     }
231 
232     @NonNull
233     @GuardedBy("mLock")
234     @VisibleForTesting
getSplitRules()235     List<EmbeddingRule> getSplitRules() {
236         return mSplitRules;
237     }
238 
239     /**
240      * Registers the split organizer callback to notify about changes to active splits.
241      * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
242      * {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
243      */
244     @Deprecated
245     @Override
setSplitInfoCallback( @onNull java.util.function.Consumer<List<SplitInfo>> callback)246     public void setSplitInfoCallback(
247             @NonNull java.util.function.Consumer<List<SplitInfo>> callback) {
248         Consumer<List<SplitInfo>> oemConsumer = callback::accept;
249         setSplitInfoCallback(oemConsumer);
250     }
251 
252     /**
253      * Registers the split organizer callback to notify about changes to active splits.
254      * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
255      */
setSplitInfoCallback(Consumer<List<SplitInfo>> callback)256     public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
257         synchronized (mLock) {
258             mEmbeddingCallback = callback;
259             updateCallbackIfNecessary();
260         }
261     }
262 
263     /**
264      * Clears the listener set in {@link SplitController#setSplitInfoCallback(Consumer)}.
265      */
266     @Override
clearSplitInfoCallback()267     public void clearSplitInfoCallback() {
268         synchronized (mLock) {
269             mEmbeddingCallback = null;
270         }
271     }
272 
273     /**
274      * Called when the transaction is ready so that the organizer can update the TaskFragments based
275      * on the changes in transaction.
276      */
277     @Override
onTransactionReady(@onNull TaskFragmentTransaction transaction)278     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
279         synchronized (mLock) {
280             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
281                     transaction.getTransactionToken());
282             final WindowContainerTransaction wct = transactionRecord.getTransaction();
283             final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
284             for (TaskFragmentTransaction.Change change : changes) {
285                 final int taskId = change.getTaskId();
286                 final TaskFragmentInfo info = change.getTaskFragmentInfo();
287                 switch (change.getType()) {
288                     case TYPE_TASK_FRAGMENT_APPEARED:
289                         mPresenter.updateTaskFragmentInfo(info);
290                         onTaskFragmentAppeared(wct, info);
291                         break;
292                     case TYPE_TASK_FRAGMENT_INFO_CHANGED:
293                         mPresenter.updateTaskFragmentInfo(info);
294                         onTaskFragmentInfoChanged(wct, info);
295                         break;
296                     case TYPE_TASK_FRAGMENT_VANISHED:
297                         mPresenter.removeTaskFragmentInfo(info);
298                         onTaskFragmentVanished(wct, info);
299                         break;
300                     case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
301                         onTaskFragmentParentInfoChanged(wct, taskId,
302                                 change.getTaskFragmentParentInfo());
303                         break;
304                     case TYPE_TASK_FRAGMENT_ERROR:
305                         final Bundle errorBundle = change.getErrorBundle();
306                         final IBinder errorToken = change.getErrorCallbackToken();
307                         final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable(
308                                 KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
309                         final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE);
310                         final Throwable exception = errorBundle.getSerializable(
311                                 KEY_ERROR_CALLBACK_THROWABLE, Throwable.class);
312                         if (errorTaskFragmentInfo != null) {
313                             mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo);
314                         }
315                         onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType,
316                                 exception);
317                         break;
318                     case TYPE_ACTIVITY_REPARENTED_TO_TASK:
319                         onActivityReparentedToTask(
320                                 wct,
321                                 taskId,
322                                 change.getActivityIntent(),
323                                 change.getActivityToken());
324                         break;
325                     default:
326                         throw new IllegalArgumentException(
327                                 "Unknown TaskFragmentEvent=" + change.getType());
328                 }
329             }
330 
331             // Notify the server, and the server should apply and merge the
332             // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
333             transactionRecord.apply(false /* shouldApplyIndependently */);
334             updateCallbackIfNecessary();
335         }
336     }
337 
338     /**
339      * Called when a TaskFragment is created and organized by this organizer.
340      *
341      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
342      * @param taskFragmentInfo  Info of the TaskFragment that is created.
343      */
344     // Suppress GuardedBy warning because lint ask to mark this method as
345     // @GuardedBy(container.mController.mLock), which is mLock itself
346     @SuppressWarnings("GuardedBy")
347     @VisibleForTesting
348     @GuardedBy("mLock")
onTaskFragmentAppeared(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)349     void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
350             @NonNull TaskFragmentInfo taskFragmentInfo) {
351         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
352         if (container == null) {
353             return;
354         }
355 
356         container.setInfo(wct, taskFragmentInfo);
357         if (container.isFinished()) {
358             mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
359             mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
360         } else {
361             // Update with the latest Task configuration.
362             updateContainer(wct, container);
363         }
364     }
365 
366     /**
367      * Called when the status of an organized TaskFragment is changed.
368      *
369      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
370      * @param taskFragmentInfo  Info of the TaskFragment that is changed.
371      */
372     // Suppress GuardedBy warning because lint ask to mark this method as
373     // @GuardedBy(container.mController.mLock), which is mLock itself
374     @SuppressWarnings("GuardedBy")
375     @VisibleForTesting
376     @GuardedBy("mLock")
onTaskFragmentInfoChanged(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)377     void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
378             @NonNull TaskFragmentInfo taskFragmentInfo) {
379         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
380         if (container == null) {
381             return;
382         }
383 
384         final boolean wasInPip = isInPictureInPicture(container);
385         container.setInfo(wct, taskFragmentInfo);
386         final boolean isInPip = isInPictureInPicture(container);
387         // Check if there are no running activities - consider the container empty if there are
388         // no non-finishing activities left.
389         if (!taskFragmentInfo.hasRunningActivity()) {
390             if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
391                 // Do not finish the dependents if the last activity is reparented to PiP.
392                 // Instead, the original split should be cleanup, and the dependent may be
393                 // expanded to fullscreen.
394                 mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
395                 cleanupForEnterPip(wct, container);
396                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
397             } else if (taskFragmentInfo.isTaskClearedForReuse()) {
398                 // Do not finish the dependents if this TaskFragment was cleared due to
399                 // launching activity in the Task.
400                 mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
401                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
402             } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
403                 // Do not finish the dependents if this TaskFragment was cleared to reorder
404                 // the launching Activity to front of the Task.
405                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
406             } else if (!container.isWaitingActivityAppear()) {
407                 // Do not finish the container before the expected activity appear until
408                 // timeout.
409                 mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
410                 mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
411             }
412         } else if (wasInPip && isInPip) {
413             // No update until exit PIP.
414             return;
415         } else if (isInPip) {
416             // Enter PIP.
417             // All overrides will be cleanup.
418             container.setLastRequestedBounds(null /* bounds */);
419             container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
420             cleanupForEnterPip(wct, container);
421         } else if (wasInPip) {
422             // Exit PIP.
423             // Updates the presentation of the container. Expand or launch placeholder if
424             // needed.
425             updateContainer(wct, container);
426         }
427     }
428 
429     /**
430      * Called when an organized TaskFragment is removed.
431      *
432      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
433      * @param taskFragmentInfo  Info of the TaskFragment that is removed.
434      */
435     @VisibleForTesting
436     @GuardedBy("mLock")
onTaskFragmentVanished(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)437     void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
438             @NonNull TaskFragmentInfo taskFragmentInfo) {
439         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
440         if (container != null) {
441             // Cleanup if the TaskFragment vanished is not requested by the organizer.
442             removeContainer(container);
443             // Make sure the containers in the Task are up-to-date.
444             updateContainersInTaskIfVisible(wct, container.getTaskId());
445         }
446         cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
447     }
448 
449     /**
450      * Called when the parent leaf Task of organized TaskFragments is changed.
451      * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
452      * transaction.
453      *
454      * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
455      * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
456      * can be an override bounds.
457      *
458      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
459      * @param taskId    Id of the parent Task that is changed.
460      * @param parentInfo  {@link TaskFragmentParentInfo} of the parent Task.
461      */
462     @VisibleForTesting
463     @GuardedBy("mLock")
onTaskFragmentParentInfoChanged(@onNull WindowContainerTransaction wct, int taskId, @NonNull TaskFragmentParentInfo parentInfo)464     void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
465             int taskId, @NonNull TaskFragmentParentInfo parentInfo) {
466         final TaskContainer taskContainer = getTaskContainer(taskId);
467         if (taskContainer == null || taskContainer.isEmpty()) {
468             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
469             return;
470         }
471         taskContainer.updateTaskFragmentParentInfo(parentInfo);
472         if (!taskContainer.isVisible()) {
473             // Don't update containers if the task is not visible. We only update containers when
474             // parentInfo#isVisibleRequested is true.
475             return;
476         }
477         if (isInPictureInPicture(parentInfo.getConfiguration())) {
478             // No need to update presentation in PIP until the Task exit PIP.
479             return;
480         }
481         updateContainersInTask(wct, taskContainer);
482     }
483 
484     @GuardedBy("mLock")
updateContainersInTaskIfVisible(@onNull WindowContainerTransaction wct, int taskId)485     void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) {
486         final TaskContainer taskContainer = getTaskContainer(taskId);
487         if (taskContainer != null && taskContainer.isVisible()) {
488             updateContainersInTask(wct, taskContainer);
489         }
490     }
491 
492     @GuardedBy("mLock")
updateContainersInTask(@onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)493     private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
494             @NonNull TaskContainer taskContainer) {
495         // Update all TaskFragments in the Task. Make a copy of the list since some may be
496         // removed on updating.
497         final List<TaskFragmentContainer> containers =
498                 new ArrayList<>(taskContainer.mContainers);
499         for (int i = containers.size() - 1; i >= 0; i--) {
500             final TaskFragmentContainer container = containers.get(i);
501             // Wait until onTaskFragmentAppeared to update new container.
502             if (!container.isFinished() && !container.isWaitingActivityAppear()) {
503                 updateContainer(wct, container);
504             }
505         }
506     }
507 
508     /**
509      * Called when an Activity is reparented to the Task with organized TaskFragment. For example,
510      * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
511      * original Task. In this case, we need to notify the organizer so that it can check if the
512      * Activity matches any split rule.
513      *
514      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
515      * @param taskId            The Task that the activity is reparented to.
516      * @param activityIntent    The intent that the activity is original launched with.
517      * @param activityToken     If the activity belongs to the same process as the organizer, this
518      *                          will be the actual activity token; if the activity belongs to a
519      *                          different process, the server will generate a temporary token that
520      *                          the organizer can use to reparent the activity through
521      *                          {@link WindowContainerTransaction} if needed.
522      */
523     @VisibleForTesting
524     @GuardedBy("mLock")
onActivityReparentedToTask(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken)525     void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
526             int taskId, @NonNull Intent activityIntent,
527             @NonNull IBinder activityToken) {
528         // If the activity belongs to the current app process, we treat it as a new activity
529         // launch.
530         final Activity activity = getActivity(activityToken);
531         if (activity != null) {
532             // We don't allow split as primary for new launch because we currently only support
533             // launching to top. We allow split as primary for activity reparent because the
534             // activity may be split as primary before it is reparented out. In that case, we
535             // want to show it as primary again when it is reparented back.
536             if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
537                 // When there is no embedding rule matched, try to place it in the top container
538                 // like a normal launch.
539                 placeActivityInTopContainer(wct, activity);
540             }
541             return;
542         }
543 
544         final TaskContainer taskContainer = getTaskContainer(taskId);
545         if (taskContainer == null || taskContainer.isInPictureInPicture()) {
546             // We don't embed activity when it is in PIP.
547             return;
548         }
549 
550         // If the activity belongs to a different app process, we treat it as starting new
551         // intent, since both actions might result in a new activity that should appear in an
552         // organized TaskFragment.
553         TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
554                 activityIntent, null /* launchingActivity */);
555         if (targetContainer == null) {
556             // When there is no embedding rule matched, try to place it in the top container
557             // like a normal launch.
558             targetContainer = taskContainer.getTopTaskFragmentContainer();
559         }
560         if (targetContainer == null) {
561             return;
562         }
563         wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
564                 activityToken);
565         // Because the activity does not belong to the organizer process, we wait until
566         // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
567     }
568 
569     /**
570      * Called when the {@link WindowContainerTransaction} created with
571      * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
572      *
573      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
574      * @param errorCallbackToken    token set in
575      *                             {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
576      * @param taskFragmentInfo  The {@link TaskFragmentInfo}. This could be {@code null} if no
577      *                          TaskFragment created.
578      * @param opType            The {@link WindowContainerTransaction.HierarchyOp} of the failed
579      *                          transaction operation.
580      * @param exception             exception from the server side.
581      */
582     // Suppress GuardedBy warning because lint ask to mark this method as
583     // @GuardedBy(container.mController.mLock), which is mLock itself
584     @SuppressWarnings("GuardedBy")
585     @VisibleForTesting
586     @GuardedBy("mLock")
onTaskFragmentError(@onNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception)587     void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
588             @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
589             int opType, @NonNull Throwable exception) {
590         Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
591         switch (opType) {
592             case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
593             case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
594                 final TaskFragmentContainer container;
595                 if (taskFragmentInfo != null) {
596                     container = getContainer(taskFragmentInfo.getFragmentToken());
597                 } else {
598                     container = null;
599                 }
600                 if (container == null) {
601                     break;
602                 }
603 
604                 // Update the latest taskFragmentInfo and perform necessary clean-up
605                 container.setInfo(wct, taskFragmentInfo);
606                 container.clearPendingAppearedActivities();
607                 if (container.isEmpty()) {
608                     mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
609                     mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
610                 }
611                 break;
612             }
613             default:
614                 Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo
615                         + ", opType = " + opType);
616         }
617     }
618 
619     /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
620     @GuardedBy("mLock")
cleanupTaskFragment(@onNull IBinder taskFragmentToken)621     private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
622         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
623             final TaskContainer taskContainer = mTaskContainers.valueAt(i);
624             if (!taskContainer.mFinishedContainer.remove(taskFragmentToken)) {
625                 continue;
626             }
627             if (taskContainer.isEmpty()) {
628                 // Cleanup the TaskContainer if it becomes empty.
629                 mTaskContainers.remove(taskContainer.getTaskId());
630             }
631             return;
632         }
633     }
634 
635     /** Returns whether the given {@link TaskContainer} may show in split. */
636     // Suppress GuardedBy warning because lint asks to mark this method as
637     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
638     @SuppressWarnings("GuardedBy")
639     @GuardedBy("mLock")
mayShowSplit(@onNull TaskContainer taskContainer)640     private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
641         // No split inside PIP.
642         if (taskContainer.isInPictureInPicture()) {
643             return false;
644         }
645         // Always assume the TaskContainer if SplitAttributesCalculator is set
646         if (mSplitAttributesCalculator != null) {
647             return true;
648         }
649         // Check if the parent container bounds can support any split rule.
650         for (EmbeddingRule rule : mSplitRules) {
651             if (!(rule instanceof SplitRule)) {
652                 continue;
653             }
654             final SplitRule splitRule = (SplitRule) rule;
655             final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
656                     taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
657             if (shouldShowSplit(splitAttributes)) {
658                 return true;
659             }
660         }
661         return false;
662     }
663 
664     @VisibleForTesting
665     @GuardedBy("mLock")
onActivityCreated(@onNull WindowContainerTransaction wct, @NonNull Activity launchedActivity)666     void onActivityCreated(@NonNull WindowContainerTransaction wct,
667             @NonNull Activity launchedActivity) {
668         // TODO(b/229680885): we don't support launching into primary yet because we want to always
669         // launch the new activity on top.
670         resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
671         updateCallbackIfNecessary();
672     }
673 
674     /**
675      * Checks if the new added activity should be routed to a particular container. It can create a
676      * new container for the activity and a new split container if necessary.
677      * @param activity      the activity that is newly added to the Task.
678      * @param isOnReparent  whether the activity is reparented to the Task instead of new launched.
679      *                      We only support to split as primary for reparented activity for now.
680      * @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or
681      *         in a state that the caller shouldn't handle.
682      */
683     @VisibleForTesting
684     @GuardedBy("mLock")
resolveActivityToContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnReparent)685     boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct,
686             @NonNull Activity activity, boolean isOnReparent) {
687         if (isInPictureInPicture(activity) || activity.isFinishing()) {
688             // We don't embed activity when it is in PIP, or finishing. Return true since we don't
689             // want any extra handling.
690             return true;
691         }
692 
693         if (!isOnReparent && getContainerWithActivity(activity) == null
694                 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
695             // We can't find the new launched activity in any recorded container, but it is
696             // currently placed in an embedded TaskFragment. This can happen in two cases:
697             // 1. the activity is embedded in another app.
698             // 2. the organizer has already requested to remove the TaskFragment.
699             // In either case, return true since we don't want any extra handling.
700             Log.d(TAG, "Activity is in a TaskFragment that is not recorded by the organizer. r="
701                     + activity);
702             return true;
703         }
704 
705         /*
706          * We will check the following to see if there is any embedding rule matched:
707          * 1. Whether the new launched activity should always expand.
708          * 2. Whether the new launched activity should launch a placeholder.
709          * 3. Whether the new launched activity has already been in a split with a rule matched
710          *    (likely done in #onStartActivity).
711          * 4. Whether the activity below (if any) should be split with the new launched activity.
712          * 5. Whether the activity split with the activity below (if any) should be split with the
713          *    new launched activity.
714          */
715 
716         // 1. Whether the new launched activity should always expand.
717         if (shouldExpand(activity, null /* intent */)) {
718             expandActivity(wct, activity);
719             return true;
720         }
721 
722         // 2. Whether the new launched activity should launch a placeholder.
723         if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) {
724             return true;
725         }
726 
727         // 3. Whether the new launched activity has already been in a split with a rule matched.
728         if (isNewActivityInSplitWithRuleMatched(activity)) {
729             return true;
730         }
731 
732         // 4. Whether the activity below (if any) should be split with the new launched activity.
733         final Activity activityBelow = findActivityBelow(activity);
734         if (activityBelow == null) {
735             // Can't find any activity below.
736             return false;
737         }
738         if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) {
739             // Have split rule of [ activityBelow | launchedActivity ].
740             return true;
741         }
742         if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) {
743             // Have split rule of [ launchedActivity | activityBelow].
744             return true;
745         }
746 
747         // 5. Whether the activity split with the activity below (if any) should be split with the
748         //    new launched activity.
749         final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
750                 activityBelow);
751         final SplitContainer topSplit = getActiveSplitForContainer(activityBelowContainer);
752         if (topSplit == null || !isTopMostSplit(topSplit)) {
753             // Skip if it is not the topmost split.
754             return false;
755         }
756         final TaskFragmentContainer otherTopContainer =
757                 topSplit.getPrimaryContainer() == activityBelowContainer
758                         ? topSplit.getSecondaryContainer()
759                         : topSplit.getPrimaryContainer();
760         final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
761         if (otherTopActivity == null || otherTopActivity == activity) {
762             // Can't find the top activity on the other split TaskFragment.
763             return false;
764         }
765         if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) {
766             // Have split rule of [ otherTopActivity | launchedActivity ].
767             return true;
768         }
769         // Have split rule of [ launchedActivity | otherTopActivity].
770         return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity);
771     }
772 
773     /**
774      * Places the given activity to the top most TaskFragment in the task if there is any.
775      */
776     @GuardedBy("mLock")
777     @VisibleForTesting
placeActivityInTopContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity)778     void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
779             @NonNull Activity activity) {
780         if (getContainerWithActivity(activity) != null) {
781             // The activity has already been put in a TaskFragment. This is likely to be done by
782             // the server when the activity is started.
783             return;
784         }
785         final int taskId = getTaskId(activity);
786         final TaskContainer taskContainer = getTaskContainer(taskId);
787         if (taskContainer == null) {
788             return;
789         }
790         final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
791         if (targetContainer == null) {
792             return;
793         }
794         targetContainer.addPendingAppearedActivity(activity);
795         wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
796                 activity.getActivityToken());
797     }
798 
799     /**
800      * Starts an activity to side of the launchingActivity with the provided split config.
801      */
802     // Suppress GuardedBy warning because lint ask to mark this method as
803     // @GuardedBy(container.mController.mLock), which is mLock itself
804     @SuppressWarnings("GuardedBy")
805     @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)806     private void startActivityToSide(@NonNull WindowContainerTransaction wct,
807             @NonNull Activity launchingActivity, @NonNull Intent intent,
808             @Nullable Bundle options, @NonNull SplitRule sideRule,
809             @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
810             boolean isPlaceholder) {
811         try {
812             mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
813                     splitAttributes, isPlaceholder);
814         } catch (Exception e) {
815             if (failureCallback != null) {
816                 failureCallback.accept(e);
817             }
818         }
819     }
820 
821     /**
822      * Expands the given activity by either expanding the TaskFragment it is currently in or putting
823      * it into a new expanded TaskFragment.
824      */
825     @GuardedBy("mLock")
expandActivity(@onNull WindowContainerTransaction wct, @NonNull Activity activity)826     private void expandActivity(@NonNull WindowContainerTransaction wct,
827             @NonNull Activity activity) {
828         final TaskFragmentContainer container = getContainerWithActivity(activity);
829         if (shouldContainerBeExpanded(container)) {
830             // Make sure that the existing container is expanded.
831             mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
832         } else {
833             // Put activity into a new expanded container.
834             final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity));
835             mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
836         }
837     }
838 
839     /** Whether the given new launched activity is in a split with a rule matched. */
840     // Suppress GuardedBy warning because lint asks to mark this method as
841     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
842     @SuppressWarnings("GuardedBy")
843     @GuardedBy("mLock")
isNewActivityInSplitWithRuleMatched(@onNull Activity launchedActivity)844     private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
845         final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
846         final SplitContainer splitContainer = getActiveSplitForContainer(container);
847         if (splitContainer == null) {
848             return false;
849         }
850 
851         if (container == splitContainer.getPrimaryContainer()) {
852             // The new launched can be in the primary container when it is starting a new activity
853             // onCreate.
854             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
855             final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent();
856             if (secondaryIntent != null) {
857                 // Check with the pending Intent before it is started on the server side.
858                 // This can happen if the launched Activity start a new Intent to secondary during
859                 // #onCreated().
860                 return getSplitRule(launchedActivity, secondaryIntent) != null;
861             }
862             final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity();
863             return secondaryActivity != null
864                     && getSplitRule(launchedActivity, secondaryActivity) != null;
865         }
866 
867         // Check if the new launched activity is a placeholder.
868         if (splitContainer.getSplitRule() instanceof SplitPlaceholderRule) {
869             final SplitPlaceholderRule placeholderRule =
870                     (SplitPlaceholderRule) splitContainer.getSplitRule();
871             final ComponentName placeholderName = placeholderRule.getPlaceholderIntent()
872                     .getComponent();
873             // TODO(b/232330767): Do we have a better way to check this?
874             return placeholderName == null
875                     || placeholderName.equals(launchedActivity.getComponentName())
876                     || placeholderRule.getPlaceholderIntent().equals(launchedActivity.getIntent());
877         }
878 
879         // Check if the new launched activity should be split with the primary top activity.
880         final Activity primaryActivity = splitContainer.getPrimaryContainer()
881                 .getTopNonFinishingActivity();
882         if (primaryActivity == null) {
883             return false;
884         }
885         /* TODO(b/231845476) we should always respect clearTop.
886         final SplitPairRule curSplitRule = (SplitPairRule) splitContainer.getSplitRule();
887         final SplitPairRule splitRule = getSplitRule(primaryActivity, launchedActivity);
888         return splitRule != null && haveSamePresentation(splitRule, curSplitRule)
889                 // If the new launched split rule should clear top and it is not the bottom most,
890                 // it means we should create a new split pair and clear the existing secondary.
891                 && (!splitRule.shouldClearTop()
892                 || container.getBottomMostActivity() == launchedActivity);
893          */
894         return getSplitRule(primaryActivity, launchedActivity) != null;
895     }
896 
897     /** Finds the activity below the given activity. */
898     @VisibleForTesting
899     @Nullable
900     @GuardedBy("mLock")
findActivityBelow(@onNull Activity activity)901     Activity findActivityBelow(@NonNull Activity activity) {
902         Activity activityBelow = null;
903         final TaskFragmentContainer container = getContainerWithActivity(activity);
904         if (container != null) {
905             final List<Activity> containerActivities = container.collectNonFinishingActivities();
906             final int index = containerActivities.indexOf(activity);
907             if (index > 0) {
908                 activityBelow = containerActivities.get(index - 1);
909             }
910         }
911         if (activityBelow == null) {
912             final IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
913                     activity.getActivityToken());
914             if (belowToken != null) {
915                 activityBelow = getActivity(belowToken);
916             }
917         }
918         return activityBelow;
919     }
920 
921     /**
922      * Checks if there is a rule to split the two activities. If there is one, puts them into split
923      * and returns {@code true}. Otherwise, returns {@code false}.
924      */
925     // Suppress GuardedBy warning because lint ask to mark this method as
926     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
927     @SuppressWarnings("GuardedBy")
928     @GuardedBy("mLock")
putActivitiesIntoSplitIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity)929     private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
930             @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
931         final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
932         if (splitRule == null) {
933             return false;
934         }
935         final TaskFragmentContainer primaryContainer = getContainerWithActivity(
936                 primaryActivity);
937         final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
938         final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
939         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
940                 && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
941             // Can launch in the existing secondary container if the rules share the same
942             // presentation.
943             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
944             if (secondaryContainer == getContainerWithActivity(secondaryActivity)) {
945                 // The activity is already in the target TaskFragment.
946                 return true;
947             }
948             secondaryContainer.addPendingAppearedActivity(secondaryActivity);
949             if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
950                     secondaryActivity, null /* secondaryIntent */)
951                     != RESULT_EXPAND_FAILED_NO_TF_INFO) {
952                 wct.reparentActivityToTaskFragment(
953                         secondaryContainer.getTaskFragmentToken(),
954                         secondaryActivity.getActivityToken());
955                 return true;
956             }
957         }
958         // Create new split pair.
959         mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
960         return true;
961     }
962 
963     @GuardedBy("mLock")
onActivityConfigurationChanged(@onNull WindowContainerTransaction wct, @NonNull Activity activity)964     private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct,
965             @NonNull Activity activity) {
966         if (activity.isFinishing()) {
967             // Do nothing if the activity is currently finishing.
968             return;
969         }
970 
971         if (isInPictureInPicture(activity)) {
972             // We don't embed activity when it is in PIP.
973             return;
974         }
975         final TaskFragmentContainer currentContainer = getContainerWithActivity(activity);
976 
977         if (currentContainer != null) {
978             // Changes to activities in controllers are handled in
979             // onTaskFragmentParentInfoChanged
980             return;
981         }
982 
983         // Check if activity requires a placeholder
984         launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
985     }
986 
987     @VisibleForTesting
988     @GuardedBy("mLock")
onActivityDestroyed(@onNull Activity activity)989     void onActivityDestroyed(@NonNull Activity activity) {
990         if (!activity.isFinishing()) {
991             // onDestroyed is triggered without finishing. This happens when the activity is
992             // relaunched. In this case, we don't want to cleanup the record.
993             return;
994         }
995         // Remove any pending appeared activity, as the server won't send finished activity to the
996         // organizer.
997         final IBinder activityToken = activity.getActivityToken();
998         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
999             mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
1000         }
1001         // We didn't trigger the callback if there were any pending appeared activities, so check
1002         // again after the pending is removed.
1003         updateCallbackIfNecessary();
1004     }
1005 
1006     /**
1007      * Called when we have been waiting too long for the TaskFragment to become non-empty after
1008      * creation.
1009      */
1010     @GuardedBy("mLock")
onTaskFragmentAppearEmptyTimeout(@onNull TaskFragmentContainer container)1011     void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
1012         final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
1013         onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
1014         // Can be applied independently as a timeout callback.
1015         transactionRecord.apply(true /* shouldApplyIndependently */);
1016     }
1017 
1018     /**
1019      * Called when we have been waiting too long for the TaskFragment to become non-empty after
1020      * creation.
1021      */
1022     @GuardedBy("mLock")
onTaskFragmentAppearEmptyTimeout(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1023     void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
1024             @NonNull TaskFragmentContainer container) {
1025         mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
1026         mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
1027     }
1028 
1029     @Nullable
1030     @GuardedBy("mLock")
resolveStartActivityIntentFromNonActivityContext( @onNull WindowContainerTransaction wct, @NonNull Intent intent)1031     private TaskFragmentContainer resolveStartActivityIntentFromNonActivityContext(
1032             @NonNull WindowContainerTransaction wct, @NonNull Intent intent) {
1033         final int taskCount = mTaskContainers.size();
1034         if (taskCount == 0) {
1035             // We don't have other Activity to check split with.
1036             return null;
1037         }
1038         if (taskCount > 1) {
1039             Log.w(TAG, "App is calling startActivity from a non-Activity context when it has"
1040                     + " more than one Task. If the new launch Activity is in a different process,"
1041                     + " and it is expected to be embedded, please start it from an Activity"
1042                     + " instead.");
1043             return null;
1044         }
1045 
1046         // Check whether the Intent should be embedded in the known Task.
1047         final TaskContainer taskContainer = mTaskContainers.valueAt(0);
1048         if (taskContainer.isInPictureInPicture()
1049                 || taskContainer.getTopNonFinishingActivity() == null) {
1050             // We don't embed activity when it is in PIP, or if we can't find any other owner
1051             // activity in the Task.
1052             return null;
1053         }
1054 
1055         return resolveStartActivityIntent(wct, taskContainer.getTaskId(), intent,
1056                 null /* launchingActivity */);
1057     }
1058 
1059     /**
1060      * When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer}
1061      * that we should reparent the new activity to if there is any embedding rule matched.
1062      *
1063      * @param wct               {@link WindowContainerTransaction} including all the window change
1064      *                          requests. The caller is responsible to call
1065      *                          {@link android.window.TaskFragmentOrganizer#applyTransaction}.
1066      * @param taskId            The Task to start the activity in.
1067      * @param intent            The {@link Intent} for starting the new launched activity.
1068      * @param launchingActivity The {@link Activity} that starts the new activity. We will
1069      *                          prioritize to split the new activity with it if it is not
1070      *                          {@code null}.
1071      * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
1072      *         is no embedding rule matched.
1073      */
1074     @VisibleForTesting
1075     @Nullable
1076     @GuardedBy("mLock")
resolveStartActivityIntent(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity)1077     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
1078             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
1079         /*
1080          * We will check the following to see if there is any embedding rule matched:
1081          * 1. Whether the new activity intent should always expand.
1082          * 2. Whether the launching activity (if set) should be split with the new activity intent.
1083          * 3. Whether the top activity (if any) should be split with the new activity intent.
1084          * 4. Whether the top activity (if any) in other split should be split with the new
1085          *    activity intent.
1086          */
1087 
1088         // 1. Whether the new activity intent should always expand.
1089         if (shouldExpand(null /* activity */, intent)) {
1090             return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity);
1091         }
1092 
1093         // 2. Whether the launching activity (if set) should be split with the new activity intent.
1094         if (launchingActivity != null) {
1095             final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
1096                     launchingActivity, intent, true /* respectClearTop */);
1097             if (container != null) {
1098                 return container;
1099             }
1100         }
1101 
1102         // 3. Whether the top activity (if any) should be split with the new activity intent.
1103         final TaskContainer taskContainer = getTaskContainer(taskId);
1104         if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
1105             // There is no other activity in the Task to check split with.
1106             return null;
1107         }
1108         final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
1109         final Activity topActivity = topContainer.getTopNonFinishingActivity();
1110         if (topActivity != null && topActivity != launchingActivity) {
1111             final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
1112                     topActivity, intent, false /* respectClearTop */);
1113             if (container != null) {
1114                 return container;
1115             }
1116         }
1117 
1118         // 4. Whether the top activity (if any) in other split should be split with the new
1119         //    activity intent.
1120         final SplitContainer topSplit = getActiveSplitForContainer(topContainer);
1121         if (topSplit == null) {
1122             return null;
1123         }
1124         final TaskFragmentContainer otherTopContainer =
1125                 topSplit.getPrimaryContainer() == topContainer
1126                         ? topSplit.getSecondaryContainer()
1127                         : topSplit.getPrimaryContainer();
1128         final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
1129         if (otherTopActivity != null && otherTopActivity != launchingActivity) {
1130             return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent,
1131                     false /* respectClearTop */);
1132         }
1133         return null;
1134     }
1135 
1136     /**
1137      * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
1138      */
1139     @GuardedBy("mLock")
1140     @Nullable
createEmptyExpandedContainer( @onNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity)1141     private TaskFragmentContainer createEmptyExpandedContainer(
1142             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
1143             @Nullable Activity launchingActivity) {
1144         // We need an activity in the organizer process in the same Task to use as the owner
1145         // activity, as well as to get the Task window info.
1146         final Activity activityInTask;
1147         if (launchingActivity != null) {
1148             activityInTask = launchingActivity;
1149         } else {
1150             final TaskContainer taskContainer = getTaskContainer(taskId);
1151             activityInTask = taskContainer != null
1152                     ? taskContainer.getTopNonFinishingActivity()
1153                     : null;
1154         }
1155         if (activityInTask == null) {
1156             // Can't find any activity in the Task that we can use as the owner activity.
1157             return null;
1158         }
1159         final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
1160                 taskId);
1161         mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
1162                 activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
1163         mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
1164                 TaskFragmentAnimationParams.DEFAULT);
1165         return expandedContainer;
1166     }
1167 
1168     /**
1169      * Returns a container for the new activity intent to launch into as splitting with the primary
1170      * activity.
1171      */
1172     @GuardedBy("mLock")
1173     @Nullable
getSecondaryContainerForSplitIfAny( @onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent intent, boolean respectClearTop)1174     private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
1175             @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
1176             @NonNull Intent intent, boolean respectClearTop) {
1177         final SplitPairRule splitRule = getSplitRule(primaryActivity, intent);
1178         if (splitRule == null) {
1179             return null;
1180         }
1181         final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
1182         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
1183         final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
1184         if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
1185                 && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
1186                 // TODO(b/231845476) we should always respect clearTop.
1187                 || !respectClearTop)
1188                 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
1189                         null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
1190             // Can launch in the existing secondary container if the rules share the same
1191             // presentation.
1192             return splitContainer.getSecondaryContainer();
1193         }
1194         // Create a new TaskFragment to split with the primary activity for the new activity.
1195         return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
1196                 splitRule);
1197     }
1198 
1199     /**
1200      * Returns a container that this activity is registered with. An activity can only belong to one
1201      * container, or no container at all.
1202      */
1203     @GuardedBy("mLock")
1204     @Nullable
getContainerWithActivity(@onNull Activity activity)1205     TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
1206         return getContainerWithActivity(activity.getActivityToken());
1207     }
1208 
1209     @GuardedBy("mLock")
1210     @Nullable
getContainerWithActivity(@onNull IBinder activityToken)1211     TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
1212         // Check pending appeared activity first because there can be a delay for the server
1213         // update.
1214         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1215             final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
1216             for (int j = containers.size() - 1; j >= 0; j--) {
1217                 final TaskFragmentContainer container = containers.get(j);
1218                 if (container.hasPendingAppearedActivity(activityToken)) {
1219                     return container;
1220                 }
1221             }
1222         }
1223 
1224         // Check appeared activity if there is no such pending appeared activity.
1225         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1226             final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
1227             for (int j = containers.size() - 1; j >= 0; j--) {
1228                 final TaskFragmentContainer container = containers.get(j);
1229                 if (container.hasAppearedActivity(activityToken)) {
1230                     return container;
1231                 }
1232             }
1233         }
1234         return null;
1235     }
1236 
1237     @GuardedBy("mLock")
newContainer(@onNull Activity pendingAppearedActivity, int taskId)1238     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) {
1239         return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId);
1240     }
1241 
1242     @GuardedBy("mLock")
newContainer(@onNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId)1243     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
1244             @NonNull Activity activityInTask, int taskId) {
1245         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
1246                 activityInTask, taskId, null /* pairedPrimaryContainer */);
1247     }
1248 
1249     @GuardedBy("mLock")
newContainer(@onNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId)1250     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
1251             @NonNull Activity activityInTask, int taskId) {
1252         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
1253                 activityInTask, taskId, null /* pairedPrimaryContainer */);
1254     }
1255 
1256     /**
1257      * Creates and registers a new organized container with an optional activity that will be
1258      * re-parented to it in a WCT.
1259      *
1260      * @param pendingAppearedActivity   the activity that will be reparented to the TaskFragment.
1261      * @param pendingAppearedIntent     the Intent that will be started in the TaskFragment.
1262      * @param activityInTask            activity in the same Task so that we can get the Task bounds
1263      *                                  if needed.
1264      * @param taskId                    parent Task of the new TaskFragment.
1265      * @param pairedPrimaryContainer    the paired primary {@link TaskFragmentContainer}. When it is
1266      *                                  set, the new container will be added right above it.
1267      */
1268     @GuardedBy("mLock")
newContainer(@ullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, @Nullable TaskFragmentContainer pairedPrimaryContainer)1269     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
1270             @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
1271             @Nullable TaskFragmentContainer pairedPrimaryContainer) {
1272         if (activityInTask == null) {
1273             throw new IllegalArgumentException("activityInTask must not be null,");
1274         }
1275         if (!mTaskContainers.contains(taskId)) {
1276             mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask));
1277         }
1278         final TaskContainer taskContainer = mTaskContainers.get(taskId);
1279         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
1280                 pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
1281         return container;
1282     }
1283 
1284     /**
1285      * Creates and registers a new split with the provided containers and configuration. Finishes
1286      * existing secondary containers if found for the given primary container.
1287      */
1288     // Suppress GuardedBy warning because lint ask to mark this method as
1289     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1290     @SuppressWarnings("GuardedBy")
1291     @GuardedBy("mLock")
registerSplit(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes)1292     void registerSplit(@NonNull WindowContainerTransaction wct,
1293             @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
1294             @NonNull TaskFragmentContainer secondaryContainer,
1295             @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
1296         final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
1297                 secondaryContainer, splitRule, splitAttributes);
1298         // Remove container later to prevent pinning escaping toast showing in lock task mode.
1299         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
1300             removeExistingSecondaryContainers(wct, primaryContainer);
1301         }
1302         primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
1303     }
1304 
1305     /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
1306     @GuardedBy("mLock")
cleanupForEnterPip(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1307     private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
1308             @NonNull TaskFragmentContainer container) {
1309         final TaskContainer taskContainer = container.getTaskContainer();
1310         if (taskContainer == null) {
1311             return;
1312         }
1313         final List<SplitContainer> splitsToRemove = new ArrayList<>();
1314         final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
1315         for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
1316             if (splitContainer.getPrimaryContainer() != container
1317                     && splitContainer.getSecondaryContainer() != container) {
1318                 continue;
1319             }
1320             splitsToRemove.add(splitContainer);
1321             final TaskFragmentContainer splitTf = splitContainer.getPrimaryContainer() == container
1322                     ? splitContainer.getSecondaryContainer()
1323                     : splitContainer.getPrimaryContainer();
1324             containersToUpdate.add(splitTf);
1325             // We don't want the PIP TaskFragment to be removed as a result of any of its dependents
1326             // being removed.
1327             splitTf.removeContainerToFinishOnExit(container);
1328             if (container.getTopNonFinishingActivity() != null) {
1329                 splitTf.removeActivityToFinishOnExit(container.getTopNonFinishingActivity());
1330             }
1331         }
1332         container.resetDependencies();
1333         taskContainer.mSplitContainers.removeAll(splitsToRemove);
1334         // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
1335         // since the split is dismissed.
1336         // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
1337         for (TaskFragmentContainer containerToUpdate : containersToUpdate) {
1338             updateContainer(wct, containerToUpdate);
1339         }
1340     }
1341 
1342     /**
1343      * Removes the container from bookkeeping records.
1344      */
removeContainer(@onNull TaskFragmentContainer container)1345     void removeContainer(@NonNull TaskFragmentContainer container) {
1346         // Remove all split containers that included this one
1347         final TaskContainer taskContainer = container.getTaskContainer();
1348         taskContainer.mContainers.remove(container);
1349         // Marked as a pending removal which will be removed after it is actually removed on the
1350         // server side (#onTaskFragmentVanished).
1351         // In this way, we can keep track of the Task bounds until we no longer have any
1352         // TaskFragment there.
1353         taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());
1354 
1355         // Cleanup any split references.
1356         final List<SplitContainer> containersToRemove = new ArrayList<>();
1357         for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
1358             if (container.equals(splitContainer.getSecondaryContainer())
1359                     || container.equals(splitContainer.getPrimaryContainer())) {
1360                 containersToRemove.add(splitContainer);
1361             }
1362         }
1363         taskContainer.mSplitContainers.removeAll(containersToRemove);
1364 
1365         // Cleanup any dependent references.
1366         for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
1367             containerToUpdate.removeContainerToFinishOnExit(container);
1368         }
1369     }
1370 
1371     /**
1372      * Removes a secondary container for the given primary container if an existing split is
1373      * already registered.
1374      */
1375     // Suppress GuardedBy warning because lint asks to mark this method as
1376     // @GuardedBy(existingSplitContainer.getSecondaryContainer().mController.mLock), which is mLock
1377     // itself
1378     @SuppressWarnings("GuardedBy")
1379     @GuardedBy("mLock")
removeExistingSecondaryContainers(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer)1380     private void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct,
1381             @NonNull TaskFragmentContainer primaryContainer) {
1382         // If the primary container was already in a split - remove the secondary container that
1383         // is now covered by the new one that replaced it.
1384         final SplitContainer existingSplitContainer = getActiveSplitForContainer(
1385                 primaryContainer);
1386         if (existingSplitContainer == null
1387                 || primaryContainer == existingSplitContainer.getSecondaryContainer()) {
1388             return;
1389         }
1390 
1391         existingSplitContainer.getSecondaryContainer().finish(
1392                 false /* shouldFinishDependent */, mPresenter, wct, this);
1393     }
1394 
1395     /**
1396      * Returns the topmost not finished container in Task of given task id.
1397      */
1398     @GuardedBy("mLock")
1399     @Nullable
getTopActiveContainer(int taskId)1400     TaskFragmentContainer getTopActiveContainer(int taskId) {
1401         final TaskContainer taskContainer = mTaskContainers.get(taskId);
1402         if (taskContainer == null) {
1403             return null;
1404         }
1405         for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
1406             final TaskFragmentContainer container = taskContainer.mContainers.get(i);
1407             if (!container.isFinished() && (container.getRunningActivityCount() > 0
1408                     // We may be waiting for the top TaskFragment to become non-empty after
1409                     // creation. In that case, we don't want to treat the TaskFragment below it as
1410                     // top active, otherwise it may incorrectly launch placeholder on top of the
1411                     // pending TaskFragment.
1412                     || container.isWaitingActivityAppear())) {
1413                 return container;
1414             }
1415         }
1416         return null;
1417     }
1418 
1419     /**
1420      * Updates the presentation of the container. If the container is part of the split or should
1421      * have a placeholder, it will also update the other part of the split.
1422      */
1423     @GuardedBy("mLock")
updateContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1424     void updateContainer(@NonNull WindowContainerTransaction wct,
1425             @NonNull TaskFragmentContainer container) {
1426         if (!container.getTaskContainer().isVisible()) {
1427             // Wait until the Task is visible to avoid unnecessary update when the Task is still in
1428             // background.
1429             return;
1430         }
1431         if (launchPlaceholderIfNecessary(wct, container)) {
1432             // Placeholder was launched, the positions will be updated when the activity is added
1433             // to the secondary container.
1434             return;
1435         }
1436         if (shouldContainerBeExpanded(container)) {
1437             if (container.getInfo() != null) {
1438                 mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
1439             }
1440             // If the info is not available yet the task fragment will be expanded when it's ready
1441             return;
1442         }
1443         SplitContainer splitContainer = getActiveSplitForContainer(container);
1444         if (splitContainer == null) {
1445             return;
1446         }
1447         if (!isTopMostSplit(splitContainer)) {
1448             // Skip position update - it isn't the topmost split.
1449             return;
1450         }
1451         if (splitContainer.getPrimaryContainer().isFinished()
1452                 || splitContainer.getSecondaryContainer().isFinished()) {
1453             // Skip position update - one or both containers are finished.
1454             return;
1455         }
1456         final TaskContainer taskContainer = splitContainer.getTaskContainer();
1457         final SplitRule splitRule = splitContainer.getSplitRule();
1458         final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
1459         final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
1460                 taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
1461         splitContainer.setSplitAttributes(splitAttributes);
1462         if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
1463             // Placeholder was finished, the positions will be updated when its container is emptied
1464             return;
1465         }
1466         mPresenter.updateSplitContainer(splitContainer, container, wct);
1467     }
1468 
1469     /** Whether the given split is the topmost split in the Task. */
isTopMostSplit(@onNull SplitContainer splitContainer)1470     private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
1471         final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
1472                 .getTaskContainer().mSplitContainers;
1473         return splitContainer == splitContainers.get(splitContainers.size() - 1);
1474     }
1475 
1476     /**
1477      * Returns the top active split container that has the provided container, if available.
1478      */
1479     @Nullable
getActiveSplitForContainer(@ullable TaskFragmentContainer container)1480     private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
1481         if (container == null) {
1482             return null;
1483         }
1484         final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
1485         if (splitContainers.isEmpty()) {
1486             return null;
1487         }
1488         for (int i = splitContainers.size() - 1; i >= 0; i--) {
1489             final SplitContainer splitContainer = splitContainers.get(i);
1490             if (container.equals(splitContainer.getSecondaryContainer())
1491                     || container.equals(splitContainer.getPrimaryContainer())) {
1492                 return splitContainer;
1493             }
1494         }
1495         return null;
1496     }
1497 
1498     /**
1499      * Returns the active split that has the provided containers as primary and secondary or as
1500      * secondary and primary, if available.
1501      */
1502     @GuardedBy("mLock")
1503     @Nullable
getActiveSplitForContainers( @onNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer)1504     SplitContainer getActiveSplitForContainers(
1505             @NonNull TaskFragmentContainer firstContainer,
1506             @NonNull TaskFragmentContainer secondContainer) {
1507         final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
1508                 .mSplitContainers;
1509         for (int i = splitContainers.size() - 1; i >= 0; i--) {
1510             final SplitContainer splitContainer = splitContainers.get(i);
1511             final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
1512             final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
1513             if ((firstContainer == secondary && secondContainer == primary)
1514                     || (firstContainer == primary && secondContainer == secondary)) {
1515                 return splitContainer;
1516             }
1517         }
1518         return null;
1519     }
1520 
1521     /**
1522      * Checks if the container requires a placeholder and launches it if necessary.
1523      */
1524     @GuardedBy("mLock")
launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1525     private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
1526             @NonNull TaskFragmentContainer container) {
1527         final Activity topActivity = container.getTopNonFinishingActivity();
1528         if (topActivity == null) {
1529             return false;
1530         }
1531 
1532         return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
1533     }
1534 
1535     // Suppress GuardedBy warning because lint ask to mark this method as
1536     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1537     @SuppressWarnings("GuardedBy")
1538     @GuardedBy("mLock")
launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnCreated)1539     boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
1540             @NonNull Activity activity, boolean isOnCreated) {
1541         if (activity.isFinishing()) {
1542             return false;
1543         }
1544 
1545         final TaskFragmentContainer container = getContainerWithActivity(activity);
1546         if (container != null && !allowLaunchPlaceholder(container)) {
1547             // We don't allow activity in this TaskFragment to launch placeholder.
1548             return false;
1549         }
1550 
1551         // Check if there is enough space for launch
1552         final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity);
1553 
1554         if (placeholderRule == null) {
1555             return false;
1556         }
1557 
1558         final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
1559         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
1560                 placeholderRule.getPlaceholderIntent());
1561         final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
1562                 placeholderRule, minDimensionsPair);
1563         if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
1564             return false;
1565         }
1566 
1567         // TODO(b/190433398): Handle failed request
1568         final Bundle options = getPlaceholderOptions(activity, isOnCreated);
1569         startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
1570                 placeholderRule, splitAttributes, null /* failureCallback */,
1571                 true /* isPlaceholder */);
1572         return true;
1573     }
1574 
1575     /** Whether or not to allow activity in this container to launch placeholder. */
1576     @GuardedBy("mLock")
allowLaunchPlaceholder(@onNull TaskFragmentContainer container)1577     private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
1578         final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
1579         if (container != topContainer) {
1580             // The container is not the top most.
1581             if (!container.isVisible()) {
1582                 // In case the container is visible (the one on top may be transparent), we may
1583                 // still want to launch placeholder even if it is not the top most.
1584                 return false;
1585             }
1586             if (topContainer.isWaitingActivityAppear()) {
1587                 // When the top container appeared info is not sent by the server yet, the visible
1588                 // check above may not be reliable.
1589                 return false;
1590             }
1591         }
1592 
1593         final SplitContainer splitContainer = getActiveSplitForContainer(container);
1594         if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
1595             // Don't launch placeholder for primary split container.
1596             return false;
1597         }
1598         return true;
1599     }
1600 
1601     /**
1602      * Gets the activity options for starting the placeholder activity. In case the placeholder is
1603      * launched when the Task is in the background, we don't want to bring the Task to the front.
1604      * @param primaryActivity   the primary activity to launch the placeholder from.
1605      * @param isOnCreated       whether this happens during the primary activity onCreated.
1606      */
1607     @VisibleForTesting
1608     @GuardedBy("mLock")
1609     @Nullable
getPlaceholderOptions(@onNull Activity primaryActivity, boolean isOnCreated)1610     Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
1611         // Setting avoid move to front will also skip the animation. We only want to do that when
1612         // the Task is currently in background.
1613         // Check if the primary is resumed or if this is called when the primary is onCreated
1614         // (not resumed yet).
1615         if (isOnCreated || primaryActivity.isResumed()) {
1616             // Only set trigger type if the launch happens in foreground.
1617             mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
1618             return null;
1619         }
1620         final ActivityOptions options = ActivityOptions.makeBasic();
1621         options.setAvoidMoveToFront();
1622         return options.toBundle();
1623     }
1624 
1625     // Suppress GuardedBy warning because lint ask to mark this method as
1626     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1627     @SuppressWarnings("GuardedBy")
1628     @VisibleForTesting
1629     @GuardedBy("mLock")
dismissPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull SplitContainer splitContainer)1630     boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
1631             @NonNull SplitContainer splitContainer) {
1632         if (!splitContainer.isPlaceholderContainer()) {
1633             return false;
1634         }
1635 
1636         if (isStickyPlaceholderRule(splitContainer.getSplitRule())) {
1637             // The placeholder should remain after it was first shown.
1638             return false;
1639         }
1640         final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
1641         if (SplitPresenter.shouldShowSplit(splitAttributes)) {
1642             return false;
1643         }
1644 
1645         mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
1646         mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
1647                 false /* shouldFinishDependent */);
1648         return true;
1649     }
1650 
1651     /**
1652      * Returns the rule to launch a placeholder for the activity with the provided component name
1653      * if it is configured in the split config.
1654      */
1655     @GuardedBy("mLock")
getPlaceholderRule(@onNull Activity activity)1656     private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
1657         for (EmbeddingRule rule : mSplitRules) {
1658             if (!(rule instanceof SplitPlaceholderRule)) {
1659                 continue;
1660             }
1661             SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule;
1662             if (placeholderRule.matchesActivity(activity)) {
1663                 return placeholderRule;
1664             }
1665         }
1666         return null;
1667     }
1668 
1669     /**
1670      * Notifies listeners about changes to split states if necessary.
1671      */
1672     @VisibleForTesting
1673     @GuardedBy("mLock")
updateCallbackIfNecessary()1674     void updateCallbackIfNecessary() {
1675         if (mEmbeddingCallback == null || !readyToReportToClient()) {
1676             return;
1677         }
1678         final List<SplitInfo> currentSplitStates = getActiveSplitStates();
1679         if (mLastReportedSplitStates.equals(currentSplitStates)) {
1680             return;
1681         }
1682         mLastReportedSplitStates.clear();
1683         mLastReportedSplitStates.addAll(currentSplitStates);
1684         mEmbeddingCallback.accept(currentSplitStates);
1685     }
1686 
1687     /**
1688      * Returns a list of descriptors for currently active split states.
1689      */
1690     @GuardedBy("mLock")
1691     @NonNull
getActiveSplitStates()1692     private List<SplitInfo> getActiveSplitStates() {
1693         final List<SplitInfo> splitStates = new ArrayList<>();
1694         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1695             mTaskContainers.valueAt(i).getSplitStates(splitStates);
1696         }
1697         return splitStates;
1698     }
1699 
1700     /**
1701      * Whether we can now report the split states to the client.
1702      */
1703     @GuardedBy("mLock")
readyToReportToClient()1704     private boolean readyToReportToClient() {
1705         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1706             if (mTaskContainers.valueAt(i).isInIntermediateState()) {
1707                 // If any Task is in an intermediate state, wait for the server update.
1708                 return false;
1709             }
1710         }
1711         return true;
1712     }
1713 
1714     /**
1715      * Returns {@code true} if the container is expanded to occupy full task size.
1716      * Returns {@code false} if the container is included in an active split.
1717      */
shouldContainerBeExpanded(@ullable TaskFragmentContainer container)1718     boolean shouldContainerBeExpanded(@Nullable TaskFragmentContainer container) {
1719         if (container == null) {
1720             return false;
1721         }
1722         return getActiveSplitForContainer(container) == null;
1723     }
1724 
1725     /**
1726      * Returns a split rule for the provided pair of primary activity and secondary activity intent
1727      * if available.
1728      */
1729     @GuardedBy("mLock")
1730     @Nullable
getSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent)1731     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
1732             @NonNull Intent secondaryActivityIntent) {
1733         for (EmbeddingRule rule : mSplitRules) {
1734             if (!(rule instanceof SplitPairRule)) {
1735                 continue;
1736             }
1737             SplitPairRule pairRule = (SplitPairRule) rule;
1738             if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) {
1739                 return pairRule;
1740             }
1741         }
1742         return null;
1743     }
1744 
1745     /**
1746      * Returns a split rule for the provided pair of primary and secondary activities if available.
1747      */
1748     @GuardedBy("mLock")
1749     @Nullable
getSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)1750     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
1751             @NonNull Activity secondaryActivity) {
1752         for (EmbeddingRule rule : mSplitRules) {
1753             if (!(rule instanceof SplitPairRule)) {
1754                 continue;
1755             }
1756             SplitPairRule pairRule = (SplitPairRule) rule;
1757             final Intent intent = secondaryActivity.getIntent();
1758             if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity)
1759                     && (intent == null
1760                     || pairRule.matchesActivityIntentPair(primaryActivity, intent))) {
1761                 return pairRule;
1762             }
1763         }
1764         return null;
1765     }
1766 
1767     @Nullable
1768     @GuardedBy("mLock")
getContainer(@onNull IBinder fragmentToken)1769     TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
1770         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1771             final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
1772             for (TaskFragmentContainer container : containers) {
1773                 if (container.getTaskFragmentToken().equals(fragmentToken)) {
1774                     return container;
1775                 }
1776             }
1777         }
1778         return null;
1779     }
1780 
1781     @Nullable
1782     @GuardedBy("mLock")
getTaskContainer(int taskId)1783     TaskContainer getTaskContainer(int taskId) {
1784         return mTaskContainers.get(taskId);
1785     }
1786 
getHandler()1787     Handler getHandler() {
1788         return mHandler;
1789     }
1790 
1791     @GuardedBy("mLock")
getTaskId(@onNull Activity activity)1792     int getTaskId(@NonNull Activity activity) {
1793         // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an
1794         // IPC call.
1795         final TaskFragmentContainer container = getContainerWithActivity(activity);
1796         return container != null ? container.getTaskId() : activity.getTaskId();
1797     }
1798 
1799     @Nullable
getActivity(@onNull IBinder activityToken)1800     Activity getActivity(@NonNull IBinder activityToken) {
1801         return ActivityThread.currentActivityThread().getActivity(activityToken);
1802     }
1803 
1804     @VisibleForTesting
getActivityStartMonitor()1805     ActivityStartMonitor getActivityStartMonitor() {
1806         return mActivityStartMonitor;
1807     }
1808 
1809     /**
1810      * Gets the token of the TaskFragment that embedded this activity. It is available as soon as
1811      * the activity is created and attached, so it can be used during {@link #onActivityCreated}
1812      * before the server notifies the organizer to avoid racing condition.
1813      */
1814     @VisibleForTesting
1815     @Nullable
getTaskFragmentTokenFromActivityClientRecord(@onNull Activity activity)1816     IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
1817         final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
1818                 .getActivityClient(activity.getActivityToken());
1819         return record != null ? record.mTaskFragmentToken : null;
1820     }
1821 
1822     /**
1823      * Returns {@code true} if an Activity with the provided component name should always be
1824      * expanded to occupy full task bounds. Such activity must not be put in a split.
1825      */
1826     @GuardedBy("mLock")
shouldExpand(@ullable Activity activity, @Nullable Intent intent)1827     private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
1828         for (EmbeddingRule rule : mSplitRules) {
1829             if (!(rule instanceof ActivityRule)) {
1830                 continue;
1831             }
1832             ActivityRule activityRule = (ActivityRule) rule;
1833             if (!activityRule.shouldAlwaysExpand()) {
1834                 continue;
1835             }
1836             if (activity != null && activityRule.matchesActivity(activity)) {
1837                 return true;
1838             } else if (intent != null && activityRule.matchesIntent(intent)) {
1839                 return true;
1840             }
1841         }
1842         return false;
1843     }
1844 
1845     /**
1846      * Checks whether the associated container should be destroyed together with a finishing
1847      * container. There is a case when primary containers for placeholders should be retained
1848      * despite the rule configuration to finish primary with secondary - if they are marked as
1849      * 'sticky' and the placeholder was finished when fully overlapping the primary container.
1850      * @return {@code true} if the associated container should be retained (and not be finished).
1851      */
1852     // Suppress GuardedBy warning because lint ask to mark this method as
1853     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
1854     @SuppressWarnings("GuardedBy")
1855     @GuardedBy("mLock")
shouldRetainAssociatedContainer(@onNull TaskFragmentContainer finishingContainer, @NonNull TaskFragmentContainer associatedContainer)1856     boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
1857             @NonNull TaskFragmentContainer associatedContainer) {
1858         SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
1859                 finishingContainer);
1860         if (splitContainer == null) {
1861             // Containers are not in the same split, no need to retain.
1862             return false;
1863         }
1864         // Find the finish behavior for the associated container
1865         int finishBehavior;
1866         SplitRule splitRule = splitContainer.getSplitRule();
1867         if (finishingContainer == splitContainer.getPrimaryContainer()) {
1868             finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule);
1869         } else {
1870             finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule);
1871         }
1872         // Decide whether the associated container should be retained based on the current
1873         // presentation mode.
1874         if (shouldShowSplit(splitContainer)) {
1875             return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
1876         } else {
1877             return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
1878         }
1879     }
1880 
1881     /**
1882      * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer)
1883      */
1884     @GuardedBy("mLock")
shouldRetainAssociatedActivity(@onNull TaskFragmentContainer finishingContainer, @NonNull Activity associatedActivity)1885     boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer,
1886             @NonNull Activity associatedActivity) {
1887         final TaskFragmentContainer associatedContainer = getContainerWithActivity(
1888                 associatedActivity);
1889         if (associatedContainer == null) {
1890             return false;
1891         }
1892 
1893         return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
1894     }
1895 
1896     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
1897 
1898         @Override
onActivityPreCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)1899         public void onActivityPreCreated(@NonNull Activity activity,
1900                 @Nullable Bundle savedInstanceState) {
1901             if (activity.isChild()) {
1902                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
1903                 // window will just be a child of the parent Activity window.
1904                 return;
1905             }
1906             synchronized (mLock) {
1907                 final IBinder activityToken = activity.getActivityToken();
1908                 final IBinder initialTaskFragmentToken =
1909                         getTaskFragmentTokenFromActivityClientRecord(activity);
1910                 // If the activity is not embedded, then it will not have an initial task fragment
1911                 // token so no further action is needed.
1912                 if (initialTaskFragmentToken == null) {
1913                     return;
1914                 }
1915                 for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
1916                     final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
1917                             .mContainers;
1918                     for (int j = containers.size() - 1; j >= 0; j--) {
1919                         final TaskFragmentContainer container = containers.get(j);
1920                         if (!container.hasActivity(activityToken)
1921                                 && container.getTaskFragmentToken()
1922                                 .equals(initialTaskFragmentToken)) {
1923                             // The onTaskFragmentInfoChanged callback containing this activity has
1924                             // not reached the client yet, so add the activity to the pending
1925                             // appeared activities.
1926                             container.addPendingAppearedActivity(activity);
1927                             return;
1928                         }
1929                     }
1930                 }
1931             }
1932         }
1933 
1934         @Override
onActivityPostCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)1935         public void onActivityPostCreated(@NonNull Activity activity,
1936                 @Nullable Bundle savedInstanceState) {
1937             if (activity.isChild()) {
1938                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
1939                 // window will just be a child of the parent Activity window.
1940                 return;
1941             }
1942             // Calling after Activity#onCreate is complete to allow the app launch something
1943             // first. In case of a configured placeholder activity we want to make sure
1944             // that we don't launch it if an activity itself already requested something to be
1945             // launched to side.
1946             synchronized (mLock) {
1947                 final TransactionRecord transactionRecord = mTransactionManager
1948                         .startNewTransaction();
1949                 transactionRecord.setOriginType(TRANSIT_OPEN);
1950                 SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
1951                         activity);
1952                 // The WCT should be applied and merged to the activity launch transition.
1953                 transactionRecord.apply(false /* shouldApplyIndependently */);
1954             }
1955         }
1956 
1957         @Override
onActivityConfigurationChanged(@onNull Activity activity)1958         public void onActivityConfigurationChanged(@NonNull Activity activity) {
1959             if (activity.isChild()) {
1960                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
1961                 // window will just be a child of the parent Activity window.
1962                 return;
1963             }
1964             synchronized (mLock) {
1965                 final TransactionRecord transactionRecord = mTransactionManager
1966                         .startNewTransaction();
1967                 SplitController.this.onActivityConfigurationChanged(
1968                         transactionRecord.getTransaction(), activity);
1969                 // The WCT should be applied and merged to the Task change transition so that the
1970                 // placeholder is launched in the same transition.
1971                 transactionRecord.apply(false /* shouldApplyIndependently */);
1972             }
1973         }
1974 
1975         @Override
onActivityPostDestroyed(@onNull Activity activity)1976         public void onActivityPostDestroyed(@NonNull Activity activity) {
1977             if (activity.isChild()) {
1978                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
1979                 // window will just be a child of the parent Activity window.
1980                 return;
1981             }
1982             synchronized (mLock) {
1983                 SplitController.this.onActivityDestroyed(activity);
1984             }
1985         }
1986     }
1987 
1988     /** Executor that posts on the main application thread. */
1989     private static class MainThreadExecutor implements Executor {
1990         private final Handler mHandler = new Handler(Looper.getMainLooper());
1991 
1992         @Override
execute(@onNull Runnable r)1993         public void execute(@NonNull Runnable r) {
1994             mHandler.post(r);
1995         }
1996     }
1997 
1998     /**
1999      * A monitor that intercepts all activity start requests originating in the client process and
2000      * can amend them to target a specific task fragment to form a split.
2001      */
2002     @VisibleForTesting
2003     class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
2004         @VisibleForTesting
2005         @GuardedBy("mLock")
2006         Intent mCurrentIntent;
2007 
2008         @Override
onStartActivity(@onNull Context who, @NonNull Intent intent, @NonNull Bundle options)2009         public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
2010                 @NonNull Intent intent, @NonNull Bundle options) {
2011             // TODO(b/232042367): Consolidate the activity create handling so that we can handle
2012             // cross-process the same as normal.
2013 
2014             final Activity launchingActivity;
2015             if (who instanceof Activity) {
2016                 // We will check if the new activity should be split with the activity that launched
2017                 // it.
2018                 final Activity activity = (Activity) who;
2019                 // For Activity that is child of another Activity (ActivityGroup), treat the parent
2020                 // Activity as the launching one because it's window will just be a child of the
2021                 // parent Activity window.
2022                 launchingActivity = activity.isChild() ? activity.getParent() : activity;
2023                 if (isInPictureInPicture(launchingActivity)) {
2024                     // We don't embed activity when it is in PIP.
2025                     return super.onStartActivity(who, intent, options);
2026                 }
2027             } else {
2028                 // When the context to start activity is not an Activity context, we will check if
2029                 // the new activity should be embedded in the known Task belonging to the organizer
2030                 // process. @see #resolveStartActivityIntentFromNonActivityContext
2031                 // It is a current security limitation that we can't access the activity info of
2032                 // other process even if it is in the same Task.
2033                 launchingActivity = null;
2034             }
2035 
2036             synchronized (mLock) {
2037                 final TransactionRecord transactionRecord = mTransactionManager
2038                         .startNewTransaction();
2039                 transactionRecord.setOriginType(TRANSIT_OPEN);
2040                 final WindowContainerTransaction wct = transactionRecord.getTransaction();
2041                 final TaskFragmentContainer launchedInTaskFragment;
2042                 if (launchingActivity != null) {
2043                     final int taskId = getTaskId(launchingActivity);
2044                     launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
2045                             launchingActivity);
2046                 } else {
2047                     launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
2048                             intent);
2049                 }
2050                 if (launchedInTaskFragment != null) {
2051                     // Make sure the WCT is applied immediately instead of being queued so that the
2052                     // TaskFragment will be ready before activity attachment.
2053                     transactionRecord.apply(false /* shouldApplyIndependently */);
2054                     // Amend the request to let the WM know that the activity should be placed in
2055                     // the dedicated container.
2056                     options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
2057                             launchedInTaskFragment.getTaskFragmentToken());
2058                     mCurrentIntent = intent;
2059                 } else {
2060                     transactionRecord.abort();
2061                 }
2062             }
2063 
2064             return super.onStartActivity(who, intent, options);
2065         }
2066 
2067         @Override
onStartActivityResult(int result, @NonNull Bundle bOptions)2068         public void onStartActivityResult(int result, @NonNull Bundle bOptions) {
2069             super.onStartActivityResult(result, bOptions);
2070             synchronized (mLock) {
2071                 if (mCurrentIntent != null && result != START_SUCCESS) {
2072                     // Clear the pending appeared intent if the activity was not started
2073                     // successfully.
2074                     final IBinder token = bOptions.getBinder(
2075                             ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
2076                     if (token != null) {
2077                         final TaskFragmentContainer container = getContainer(token);
2078                         if (container != null) {
2079                             container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
2080                         }
2081                     }
2082                 }
2083                 mCurrentIntent = null;
2084             }
2085         }
2086     }
2087 
2088     /**
2089      * Checks if an activity is embedded and its presentation is customized by a
2090      * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
2091      */
2092     @Override
isActivityEmbedded(@onNull Activity activity)2093     public boolean isActivityEmbedded(@NonNull Activity activity) {
2094         synchronized (mLock) {
2095             return mPresenter.isActivityEmbedded(activity.getActivityToken());
2096         }
2097     }
2098 
2099     /**
2100      * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
2101      * there is any.
2102      */
canReuseContainer(@onNull SplitRule rule1, @NonNull SplitRule rule2, @NonNull WindowMetrics parentWindowMetrics)2103     private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
2104             @NonNull WindowMetrics parentWindowMetrics) {
2105         if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
2106             return false;
2107         }
2108         return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
2109                 parentWindowMetrics);
2110     }
2111 
2112     /** Whether the two rules have the same presentation. */
2113     @VisibleForTesting
haveSamePresentation(@onNull SplitPairRule rule1, @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics)2114     static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
2115             @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
2116         if (rule1.getTag() != null || rule2.getTag() != null) {
2117             // Tag must be unique if it is set. We don't want to reuse the container if the rules
2118             // have different tags because they can have different SplitAttributes later through
2119             // SplitAttributesCalculator.
2120             return Objects.equals(rule1.getTag(), rule2.getTag());
2121         }
2122         // If both rules don't have tag, compare all SplitRules' properties that may affect their
2123         // SplitAttributes.
2124         // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
2125         return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
2126                 && rule1.checkParentMetrics(parentWindowMetrics)
2127                 == rule2.checkParentMetrics(parentWindowMetrics)
2128                 && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
2129                 && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
2130     }
2131 
2132     /**
2133      * Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given
2134      * rule.
2135      */
isContainerReusableRule(@onNull SplitRule rule)2136     private static boolean isContainerReusableRule(@NonNull SplitRule rule) {
2137         // We don't expect to reuse the placeholder rule.
2138         if (!(rule instanceof SplitPairRule)) {
2139             return false;
2140         }
2141         final SplitPairRule pairRule = (SplitPairRule) rule;
2142 
2143         // Not reuse if it needs to destroy the existing.
2144         return !pairRule.shouldClearTop();
2145     }
2146 
isInPictureInPicture(@onNull Activity activity)2147     private static boolean isInPictureInPicture(@NonNull Activity activity) {
2148         return isInPictureInPicture(activity.getResources().getConfiguration());
2149     }
2150 
isInPictureInPicture(@onNull TaskFragmentContainer tf)2151     private static boolean isInPictureInPicture(@NonNull TaskFragmentContainer tf) {
2152         return isInPictureInPicture(tf.getInfo().getConfiguration());
2153     }
2154 
isInPictureInPicture(@ullable Configuration configuration)2155     private static boolean isInPictureInPicture(@Nullable Configuration configuration) {
2156         return configuration != null
2157                 && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
2158     }
2159 
2160     @Override
setLaunchingActivityStack(@onNull ActivityOptions options, @NonNull IBinder token)2161     public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
2162             @NonNull IBinder token) {
2163         throw new UnsupportedOperationException(
2164                 "setLaunchingActivityStack is not supported in API_VERSION=2");
2165     }
2166 
2167     @Override
finishActivityStacks(@onNull Set<IBinder> activityStackTokens)2168     public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
2169         throw new UnsupportedOperationException(
2170                 "finishActivityStacks is not supported in API_VERSION=2");
2171     }
2172 
2173     @Override
invalidateTopVisibleSplitAttributes()2174     public void invalidateTopVisibleSplitAttributes() {
2175         throw new UnsupportedOperationException(
2176                 "invalidateTopVisibleSplitAttributes is not supported in API_VERSION=2");
2177     }
2178 
2179     @Override
updateSplitAttributes(@onNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes)2180     public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
2181             @NonNull SplitAttributes splitAttributes) {
2182         throw new UnsupportedOperationException(
2183                 "updateSplitAttributes is not supported in API_VERSION=2");
2184     }
2185 }
2186