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