1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.window.extensions.embedding; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 21 import android.app.Activity; 22 import android.app.ActivityThread; 23 import android.app.WindowConfiguration.WindowingMode; 24 import android.content.Intent; 25 import android.graphics.Rect; 26 import android.os.Binder; 27 import android.os.Bundle; 28 import android.os.IBinder; 29 import android.util.Size; 30 import android.window.TaskFragmentAnimationParams; 31 import android.window.TaskFragmentInfo; 32 import android.window.WindowContainerTransaction; 33 34 import androidx.annotation.GuardedBy; 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.window.flags.Flags; 40 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.Iterator; 44 import java.util.List; 45 import java.util.Objects; 46 47 /** 48 * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment 49 * on the server side. 50 */ 51 // Suppress GuardedBy warning because all the TaskFragmentContainers are stored in 52 // SplitController.mTaskContainers which is guarded. 53 @SuppressWarnings("GuardedBy") 54 class TaskFragmentContainer { 55 private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000; 56 57 @NonNull 58 private final SplitController mController; 59 60 /** 61 * Client-created token that uniquely identifies the task fragment container instance. 62 */ 63 @NonNull 64 private final IBinder mToken; 65 66 /** Parent leaf Task. */ 67 @NonNull 68 private final TaskContainer mTaskContainer; 69 70 /** 71 * Server-provided task fragment information. 72 */ 73 @VisibleForTesting 74 TaskFragmentInfo mInfo; 75 76 /** 77 * Activity tokens that are being reparented or being started to this container, but haven't 78 * been added to {@link #mInfo} yet. 79 */ 80 @VisibleForTesting 81 final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>(); 82 83 /** 84 * When this container is created for an {@link Intent} to start within, we store that Intent 85 * until the container becomes non-empty on the server side, so that we can use it to check 86 * rules associated with this container. 87 */ 88 @Nullable 89 private Intent mPendingAppearedIntent; 90 91 /** 92 * The activities that were explicitly requested to be launched in its current TaskFragment, 93 * but haven't been added to {@link #mInfo} yet. 94 */ 95 final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>(); 96 97 /** Containers that are dependent on this one and should be completely destroyed on exit. */ 98 private final List<TaskFragmentContainer> mContainersToFinishOnExit = 99 new ArrayList<>(); 100 101 /** 102 * Individual associated activity tokens in different containers that should be finished on 103 * exit. 104 */ 105 private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>(); 106 107 @Nullable 108 private final String mOverlayTag; 109 110 /** 111 * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()} 112 * for {@link #isOverlay()} container. 113 */ 114 @NonNull 115 private final Bundle mLaunchOptions = new Bundle(); 116 117 /** 118 * The associated {@link Activity#getActivityToken()} of the overlay container. 119 * Must be {@code null} for non-overlay container. 120 * <p> 121 * If an overlay container is associated with an activity, this overlay container will be 122 * dismissed when the associated activity is destroyed. If the overlay container is visible, 123 * activity will be launched on top of the overlay container and expanded to fill the parent 124 * container. 125 */ 126 @Nullable 127 private final IBinder mAssociatedActivityToken; 128 129 /** Indicates whether the container was cleaned up after the last activity was removed. */ 130 private boolean mIsFinished; 131 132 /** 133 * Bounds that were requested last via {@link android.window.WindowContainerTransaction}. 134 */ 135 private final Rect mLastRequestedBounds = new Rect(); 136 137 /** 138 * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}. 139 */ 140 @WindowingMode 141 private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED; 142 143 /** 144 * TaskFragmentAnimationParams that was requested last via 145 * {@link android.window.WindowContainerTransaction}. 146 */ 147 @NonNull 148 private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT; 149 150 /** 151 * TaskFragment token that was requested last via 152 * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}. 153 */ 154 @Nullable 155 private IBinder mLastAdjacentTaskFragment; 156 157 /** 158 * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last 159 * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}. 160 */ 161 @Nullable 162 private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams; 163 164 /** 165 * TaskFragment token that was requested last via 166 * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}. 167 */ 168 @Nullable 169 private IBinder mLastCompanionTaskFragment; 170 171 /** 172 * When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment 173 * if it is still empty after the timeout. 174 */ 175 @VisibleForTesting 176 @Nullable 177 Runnable mAppearEmptyTimeout; 178 179 /** 180 * Whether this TaskFragment contains activities of another process/package. 181 */ 182 private boolean mHasCrossProcessActivities; 183 184 /** Whether this TaskFragment enable isolated navigation. */ 185 private boolean mIsIsolatedNavigationEnabled; 186 187 /** 188 * Whether this TaskFragment is pinned. 189 */ 190 private boolean mIsPinned; 191 192 /** 193 * Whether to apply dimming on the parent Task that was requested last. 194 */ 195 private boolean mLastDimOnTask; 196 197 /** 198 * Creates a container with an existing activity that will be re-parented to it in a window 199 * container transaction. 200 * @param pairedPrimaryContainer when it is set, the new container will be add right above it 201 * @param overlayTag Sets to indicate this taskFragment is an overlay container 202 * @param launchOptions The launch options to create this container. Must not be 203 * {@code null} for an overlay container 204 * @param associatedActivity the associated activity of the overlay container. Must be 205 * {@code null} for a non-overlay container. 206 */ TaskFragmentContainer(@ullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, @Nullable Bundle launchOptions, @Nullable Activity associatedActivity)207 private TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, 208 @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, 209 @NonNull SplitController controller, 210 @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, 211 @Nullable Bundle launchOptions, @Nullable Activity associatedActivity) { 212 if ((pendingAppearedActivity == null && pendingAppearedIntent == null) 213 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) { 214 throw new IllegalArgumentException( 215 "One and only one of pending activity and intent must be non-null"); 216 } 217 mController = controller; 218 mToken = new Binder("TaskFragmentContainer"); 219 mTaskContainer = taskContainer; 220 mOverlayTag = overlayTag; 221 mAssociatedActivityToken = associatedActivity != null 222 ? associatedActivity.getActivityToken() : null; 223 224 if (launchOptions != null) { 225 mLaunchOptions.putAll(launchOptions); 226 } 227 228 if (pairedPrimaryContainer != null) { 229 // The TaskFragment will be positioned right above the paired container. 230 if (pairedPrimaryContainer.getTaskContainer() != taskContainer) { 231 throw new IllegalArgumentException( 232 "pairedPrimaryContainer must be in the same Task"); 233 } 234 final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer); 235 taskContainer.addTaskFragmentContainer(primaryIndex + 1, this); 236 } else if (pendingAppearedActivity != null) { 237 // The TaskFragment will be positioned right above the pending appeared Activity. If any 238 // existing TaskFragment is empty with pending Intent, it is likely that the Activity of 239 // the pending Intent hasn't been created yet, so the new Activity should be below the 240 // empty TaskFragment. 241 final List<TaskFragmentContainer> containers = 242 taskContainer.getTaskFragmentContainers(); 243 int i = containers.size() - 1; 244 for (; i >= 0; i--) { 245 final TaskFragmentContainer container = containers.get(i); 246 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) { 247 break; 248 } 249 } 250 taskContainer.addTaskFragmentContainer(i + 1, this); 251 } else { 252 taskContainer.addTaskFragmentContainer(this); 253 } 254 if (pendingAppearedActivity != null) { 255 addPendingAppearedActivity(pendingAppearedActivity); 256 } 257 mPendingAppearedIntent = pendingAppearedIntent; 258 259 // Save the information necessary for restoring the overlay when needed. 260 if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null 261 && associatedActivity != null && !associatedActivity.isFinishing()) { 262 final IBinder associatedActivityToken = associatedActivity.getActivityToken(); 263 final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken, 264 launchOptions, pendingAppearedIntent); 265 mController.mOverlayRestoreParams.put(associatedActivityToken, params); 266 } 267 } 268 269 /** 270 * Returns the client-created token that uniquely identifies this container. 271 */ 272 @NonNull getTaskFragmentToken()273 IBinder getTaskFragmentToken() { 274 return mToken; 275 } 276 277 /** List of non-finishing activities that belong to this container and live in this process. */ 278 @NonNull collectNonFinishingActivities()279 List<Activity> collectNonFinishingActivities() { 280 final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */); 281 if (activities == null) { 282 throw new IllegalStateException( 283 "Result activities should never be null when checkIfstable is false."); 284 } 285 return activities; 286 } 287 288 /** 289 * Collects non-finishing activities that belong to this container and live in this process. 290 * 291 * @param checkIfStable if {@code true}, returns {@code null} when the container is in an 292 * intermediate state. 293 * @return List of non-finishing activities that belong to this container and live in this 294 * process, {@code null} if checkIfStable is {@code true} and the container is in an 295 * intermediate state. 296 */ 297 @Nullable collectNonFinishingActivities(boolean checkIfStable)298 List<Activity> collectNonFinishingActivities(boolean checkIfStable) { 299 if (checkIfStable 300 && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) { 301 return null; 302 } 303 304 final List<Activity> allActivities = new ArrayList<>(); 305 if (mInfo != null) { 306 // Add activities reported from the server. 307 for (IBinder token : mInfo.getActivities()) { 308 final Activity activity = mController.getActivity(token); 309 if (activity != null && !activity.isFinishing()) { 310 allActivities.add(activity); 311 } else { 312 if (checkIfStable) { 313 // Return null except for a special case when the activity is started in 314 // background. 315 if (activity == null && !mTaskContainer.isVisible()) { 316 continue; 317 } 318 return null; 319 } 320 } 321 } 322 } 323 324 // Add the re-parenting activity, in case the server has not yet reported the task 325 // fragment info update with it placed in this container. We still want to apply rules 326 // in this intermediate state. 327 // Place those on top of the list since they will be on the top after reported from the 328 // server. 329 for (IBinder token : mPendingAppearedActivities) { 330 final Activity activity = mController.getActivity(token); 331 if (activity != null && !activity.isFinishing()) { 332 allActivities.add(activity); 333 } 334 } 335 return allActivities; 336 } 337 338 /** Whether this TaskFragment is visible. */ isVisible()339 boolean isVisible() { 340 return mInfo != null && mInfo.isVisible(); 341 } 342 343 /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/ isInIntermediateState()344 boolean isInIntermediateState() { 345 if (mInfo == null) { 346 // Haven't received onTaskFragmentAppeared event. 347 return true; 348 } 349 if (mInfo.isEmpty()) { 350 // Empty TaskFragment will be removed or will have activity launched into it soon. 351 return true; 352 } 353 if (!mPendingAppearedActivities.isEmpty()) { 354 // Reparented activity hasn't appeared. 355 return true; 356 } 357 // Check if there is any reported activity that is no longer alive. 358 for (IBinder token : mInfo.getActivities()) { 359 final Activity activity = mController.getActivity(token); 360 if (activity == null && !mTaskContainer.isVisible()) { 361 // Activity can be null if the activity is not attached to process yet. That can 362 // happen when the activity is started in background. 363 continue; 364 } 365 if (activity == null || activity.isFinishing()) { 366 // One of the reported activity is no longer alive, wait for the server update. 367 return true; 368 } 369 } 370 return false; 371 } 372 373 /** 374 * Returns the ActivityStack representing this container. 375 * 376 * @return ActivityStack representing this container if it is in a stable state. {@code null} if 377 * in an intermediate state. 378 */ 379 @Nullable toActivityStackIfStable()380 ActivityStack toActivityStackIfStable() { 381 final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */); 382 if (activities == null) { 383 return null; 384 } 385 return new ActivityStack(activities, isEmpty(), 386 ActivityStack.Token.createFromBinder(mToken), mOverlayTag); 387 } 388 389 /** Adds the activity that will be reparented to this container. */ addPendingAppearedActivity(@onNull Activity pendingAppearedActivity)390 void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { 391 final IBinder activityToken = pendingAppearedActivity.getActivityToken(); 392 if (hasActivity(activityToken)) { 393 return; 394 } 395 // Remove the pending activity from other TaskFragments in case the activity is reparented 396 // again before the server update. 397 mTaskContainer.cleanupPendingAppearedActivity(activityToken); 398 mPendingAppearedActivities.add(activityToken); 399 updateActivityClientRecordTaskFragmentToken(activityToken); 400 } 401 402 /** 403 * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the 404 * activity. This makes sure the token is up-to-date if the activity is relaunched later. 405 */ updateActivityClientRecordTaskFragmentToken(@onNull IBinder activityToken)406 private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) { 407 final ActivityThread.ActivityClientRecord record = ActivityThread 408 .currentActivityThread().getActivityClient(activityToken); 409 if (record != null) { 410 record.mTaskFragmentToken = mToken; 411 } 412 } 413 removePendingAppearedActivity(@onNull IBinder activityToken)414 void removePendingAppearedActivity(@NonNull IBinder activityToken) { 415 mPendingAppearedActivities.remove(activityToken); 416 // Also remove the activity from the mPendingInRequestedTaskFragmentActivities. 417 mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken); 418 } 419 420 @GuardedBy("mController.mLock") clearPendingAppearedActivities()421 void clearPendingAppearedActivities() { 422 final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities); 423 // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the 424 // current TaskFragment. 425 mPendingAppearedActivities.clear(); 426 mPendingAppearedIntent = null; 427 428 // For removed pending activities, we need to update the them to their previous containers. 429 for (IBinder activityToken : cleanupActivities) { 430 final TaskFragmentContainer curContainer = mController.getContainerWithActivity( 431 activityToken); 432 if (curContainer != null) { 433 curContainer.updateActivityClientRecordTaskFragmentToken(activityToken); 434 } 435 } 436 } 437 438 /** Called when the activity {@link Activity#isFinishing()} and paused. */ onFinishingActivityPaused(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)439 void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct, 440 @NonNull IBinder activityToken) { 441 finishSelfWithActivityIfNeeded(wct, activityToken); 442 } 443 444 /** Called when the activity is destroyed. */ onActivityDestroyed(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)445 void onActivityDestroyed(@NonNull WindowContainerTransaction wct, 446 @NonNull IBinder activityToken) { 447 removePendingAppearedActivity(activityToken); 448 if (mInfo != null) { 449 // Remove the activity now because there can be a delay before the server callback. 450 mInfo.getActivities().remove(activityToken); 451 } 452 mActivitiesToFinishOnExit.remove(activityToken); 453 finishSelfWithActivityIfNeeded(wct, activityToken); 454 } 455 456 @VisibleForTesting finishSelfWithActivityIfNeeded(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)457 void finishSelfWithActivityIfNeeded(@NonNull WindowContainerTransaction wct, 458 @NonNull IBinder activityToken) { 459 if (mIsFinished) { 460 return; 461 } 462 // Early return if this container is not an overlay with activity association. 463 if (!isOverlayWithActivityAssociation()) { 464 return; 465 } 466 if (mAssociatedActivityToken == activityToken) { 467 // If the associated activity is destroyed, also finish this overlay container. 468 mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */); 469 } 470 } 471 472 @Nullable getPendingAppearedIntent()473 Intent getPendingAppearedIntent() { 474 return mPendingAppearedIntent; 475 } 476 setPendingAppearedIntent(@ullable Intent intent)477 void setPendingAppearedIntent(@Nullable Intent intent) { 478 mPendingAppearedIntent = intent; 479 } 480 481 /** 482 * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the 483 * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has 484 * running activities). 485 */ clearPendingAppearedIntentIfNeeded(@onNull Intent intent)486 void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) { 487 if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) { 488 return; 489 } 490 mPendingAppearedIntent = null; 491 } 492 hasActivity(@onNull IBinder activityToken)493 boolean hasActivity(@NonNull IBinder activityToken) { 494 // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make 495 // sure the controller considers this container as the one containing the activity. 496 // This is needed when the activity is added as pending appeared activity to one 497 // TaskFragment while it is also an appeared activity in another. 498 return mTaskContainer.getContainerWithActivity(activityToken) == this; 499 } 500 501 /** Whether this activity has appeared in the TaskFragment on the server side. */ hasAppearedActivity(@onNull IBinder activityToken)502 boolean hasAppearedActivity(@NonNull IBinder activityToken) { 503 return mInfo != null && mInfo.getActivities().contains(activityToken); 504 } 505 506 /** 507 * Whether we are waiting for this activity to appear in the TaskFragment on the server side. 508 */ hasPendingAppearedActivity(@onNull IBinder activityToken)509 boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) { 510 return mPendingAppearedActivities.contains(activityToken); 511 } 512 getRunningActivityCount()513 int getRunningActivityCount() { 514 int count = mPendingAppearedActivities.size(); 515 if (mInfo != null) { 516 count += mInfo.getRunningActivityCount(); 517 } 518 return count; 519 } 520 521 /** Whether we are waiting for the TaskFragment to appear and become non-empty. */ isWaitingActivityAppear()522 boolean isWaitingActivityAppear() { 523 return !mIsFinished && (mInfo == null || mAppearEmptyTimeout != null); 524 } 525 526 @Nullable getInfo()527 TaskFragmentInfo getInfo() { 528 return mInfo; 529 } 530 531 @GuardedBy("mController.mLock") setInfo(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info)532 void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) { 533 if (!mIsFinished && mInfo == null && info.isEmpty()) { 534 // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no 535 // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if 536 // it is still empty after timeout. 537 if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) { 538 mAppearEmptyTimeout = () -> { 539 synchronized (mController.mLock) { 540 mAppearEmptyTimeout = null; 541 // Call without the pass-in wct when timeout. We need to applyWct directly 542 // in this case. 543 mController.onTaskFragmentAppearEmptyTimeout(this); 544 } 545 }; 546 mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); 547 } else { 548 mAppearEmptyTimeout = null; 549 mController.onTaskFragmentAppearEmptyTimeout(wct, this); 550 } 551 } else if (mAppearEmptyTimeout != null && !info.isEmpty()) { 552 mController.getHandler().removeCallbacks(mAppearEmptyTimeout); 553 mAppearEmptyTimeout = null; 554 } 555 556 mHasCrossProcessActivities = false; 557 mInfo = info; 558 if (mInfo == null || mInfo.isEmpty()) { 559 return; 560 } 561 562 // Contains activities of another process if the activities size is not matched to the 563 // running activity count 564 if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) { 565 mHasCrossProcessActivities = true; 566 } 567 568 // Only track the pending Intent when the container is empty. 569 mPendingAppearedIntent = null; 570 if (mPendingAppearedActivities.isEmpty()) { 571 return; 572 } 573 // Cleanup activities that were being re-parented 574 List<IBinder> infoActivities = mInfo.getActivities(); 575 for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) { 576 final IBinder activityToken = mPendingAppearedActivities.get(i); 577 if (infoActivities.contains(activityToken)) { 578 removePendingAppearedActivity(activityToken); 579 } 580 } 581 } 582 583 @Nullable getTopNonFinishingActivity()584 Activity getTopNonFinishingActivity() { 585 final List<Activity> activities = collectNonFinishingActivities(); 586 return activities.isEmpty() ? null : activities.get(activities.size() - 1); 587 } 588 589 @Nullable getBottomMostActivity()590 Activity getBottomMostActivity() { 591 final List<Activity> activities = collectNonFinishingActivities(); 592 return activities.isEmpty() ? null : activities.get(0); 593 } 594 isEmpty()595 boolean isEmpty() { 596 return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty()); 597 } 598 599 /** 600 * Adds a container that should be finished when this container is finished. 601 */ addContainerToFinishOnExit(@onNull TaskFragmentContainer containerToFinish)602 void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) { 603 if (mIsFinished) { 604 return; 605 } 606 mContainersToFinishOnExit.add(containerToFinish); 607 } 608 609 /** 610 * Removes a container that should be finished when this container is finished. 611 */ removeContainerToFinishOnExit(@onNull TaskFragmentContainer containerToRemove)612 void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) { 613 removeContainersToFinishOnExit(Collections.singletonList(containerToRemove)); 614 } 615 616 /** 617 * Removes container list that should be finished when this container is finished. 618 */ removeContainersToFinishOnExit(@onNull List<TaskFragmentContainer> containersToRemove)619 void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) { 620 if (mIsFinished) { 621 return; 622 } 623 mContainersToFinishOnExit.removeAll(containersToRemove); 624 } 625 626 /** 627 * Adds an activity that should be finished when this container is finished. 628 */ addActivityToFinishOnExit(@onNull Activity activityToFinish)629 void addActivityToFinishOnExit(@NonNull Activity activityToFinish) { 630 if (mIsFinished) { 631 return; 632 } 633 mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken()); 634 } 635 636 /** 637 * Removes an activity that should be finished when this container is finished. 638 */ removeActivityToFinishOnExit(@onNull Activity activityToRemove)639 void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) { 640 if (mIsFinished) { 641 return; 642 } 643 mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken()); 644 } 645 646 /** Removes all dependencies that should be finished when this container is finished. */ resetDependencies()647 void resetDependencies() { 648 if (mIsFinished) { 649 return; 650 } 651 mContainersToFinishOnExit.clear(); 652 mActivitiesToFinishOnExit.clear(); 653 } 654 655 /** 656 * Removes all activities that belong to this process and finishes other containers/activities 657 * configured to finish together. 658 */ 659 // Suppress GuardedBy warning because lint ask to mark this method as 660 // @GuardedBy(container.mController.mLock), which is mLock itself 661 @SuppressWarnings("GuardedBy") 662 @GuardedBy("mController.mLock") finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)663 void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, 664 @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { 665 finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */); 666 } 667 668 /** 669 * Removes all activities that belong to this process and finishes other containers/activities 670 * configured to finish together. 671 */ finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller, boolean shouldRemoveRecord)672 void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, 673 @NonNull WindowContainerTransaction wct, @NonNull SplitController controller, 674 boolean shouldRemoveRecord) { 675 if (!mIsFinished) { 676 mIsFinished = true; 677 if (mAppearEmptyTimeout != null) { 678 mController.getHandler().removeCallbacks(mAppearEmptyTimeout); 679 mAppearEmptyTimeout = null; 680 } 681 finishActivities(shouldFinishDependent, presenter, wct, controller); 682 } 683 684 if (mInfo == null) { 685 // Defer removal the container and wait until TaskFragment appeared. 686 return; 687 } 688 689 // Cleanup the visuals 690 presenter.deleteTaskFragment(wct, getTaskFragmentToken()); 691 if (shouldRemoveRecord) { 692 // Cleanup the records 693 controller.removeContainer(this); 694 } 695 // Clean up task fragment information 696 mInfo = null; 697 } 698 699 @GuardedBy("mController.mLock") finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)700 private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, 701 @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { 702 // Finish own activities 703 for (Activity activity : collectNonFinishingActivities()) { 704 if (!activity.isFinishing() 705 // In case we have requested to reparent the activity to another container (as 706 // pendingAppeared), we don't want to finish it with this container. 707 && mController.getContainerWithActivity(activity) == this) { 708 wct.finishActivity(activity.getActivityToken()); 709 } 710 } 711 712 if (!shouldFinishDependent) { 713 // Always finish the placeholder when the primary is finished. 714 finishPlaceholderIfAny(wct, presenter); 715 return; 716 } 717 718 // Finish dependent containers 719 for (TaskFragmentContainer container : mContainersToFinishOnExit) { 720 if (container.mIsFinished 721 || controller.shouldRetainAssociatedContainer(this, container)) { 722 continue; 723 } 724 container.finish(true /* shouldFinishDependent */, presenter, 725 wct, controller); 726 } 727 mContainersToFinishOnExit.clear(); 728 729 // Finish associated activities 730 for (IBinder activityToken : mActivitiesToFinishOnExit) { 731 final Activity activity = mController.getActivity(activityToken); 732 if (activity == null || activity.isFinishing() 733 || controller.shouldRetainAssociatedActivity(this, activity)) { 734 continue; 735 } 736 wct.finishActivity(activity.getActivityToken()); 737 } 738 mActivitiesToFinishOnExit.clear(); 739 } 740 741 @GuardedBy("mController.mLock") finishPlaceholderIfAny(@onNull WindowContainerTransaction wct, @NonNull SplitPresenter presenter)742 private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct, 743 @NonNull SplitPresenter presenter) { 744 final List<TaskFragmentContainer> containersToRemove = new ArrayList<>(); 745 for (TaskFragmentContainer container : mContainersToFinishOnExit) { 746 if (container.mIsFinished) { 747 continue; 748 } 749 final SplitContainer splitContainer = mController.getActiveSplitForContainers( 750 this, container); 751 if (splitContainer != null && splitContainer.isPlaceholderContainer() 752 && splitContainer.getSecondaryContainer() == container) { 753 // Remove the placeholder secondary TaskFragment. 754 containersToRemove.add(container); 755 } 756 } 757 mContainersToFinishOnExit.removeAll(containersToRemove); 758 for (TaskFragmentContainer container : containersToRemove) { 759 container.finish(false /* shouldFinishDependent */, presenter, wct, mController); 760 } 761 } 762 isFinished()763 boolean isFinished() { 764 return mIsFinished; 765 } 766 767 /** 768 * Checks if last requested bounds are equal to the provided value. 769 * The requested bounds are relative bounds in parent coordinate. 770 * @see WindowContainerTransaction#setRelativeBounds 771 */ areLastRequestedBoundsEqual(@ullable Rect relBounds)772 boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) { 773 return (relBounds == null && mLastRequestedBounds.isEmpty()) 774 || mLastRequestedBounds.equals(relBounds); 775 } 776 777 /** 778 * Updates the last requested bounds. 779 * The requested bounds are relative bounds in parent coordinate. 780 * @see WindowContainerTransaction#setRelativeBounds 781 */ setLastRequestedBounds(@ullable Rect relBounds)782 void setLastRequestedBounds(@Nullable Rect relBounds) { 783 if (relBounds == null) { 784 mLastRequestedBounds.setEmpty(); 785 } else { 786 mLastRequestedBounds.set(relBounds); 787 } 788 } 789 getLastRequestedBounds()790 @NonNull Rect getLastRequestedBounds() { 791 return mLastRequestedBounds; 792 } 793 794 /** 795 * Checks if last requested windowing mode is equal to the provided value. 796 * @see WindowContainerTransaction#setWindowingMode 797 */ isLastRequestedWindowingModeEqual(@indowingMode int windowingMode)798 boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) { 799 return mLastRequestedWindowingMode == windowingMode; 800 } 801 802 /** 803 * Updates the last requested windowing mode. 804 * @see WindowContainerTransaction#setWindowingMode 805 */ setLastRequestedWindowingMode(@indowingMode int windowingModes)806 void setLastRequestedWindowingMode(@WindowingMode int windowingModes) { 807 mLastRequestedWindowingMode = windowingModes; 808 } 809 810 /** 811 * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value. 812 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS 813 */ areLastRequestedAnimationParamsEqual( @onNull TaskFragmentAnimationParams animationParams)814 boolean areLastRequestedAnimationParamsEqual( 815 @NonNull TaskFragmentAnimationParams animationParams) { 816 return mLastAnimationParams.equals(animationParams); 817 } 818 819 /** 820 * Updates the last requested {@link TaskFragmentAnimationParams}. 821 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS 822 */ setLastRequestAnimationParams(@onNull TaskFragmentAnimationParams animationParams)823 void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) { 824 mLastAnimationParams = animationParams; 825 } 826 827 /** 828 * Checks if last requested adjacent TaskFragment token and params are equal to the provided 829 * values. 830 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS 831 * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS 832 */ isLastAdjacentTaskFragmentEqual(@ullable IBinder fragmentToken, @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params)833 boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken, 834 @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) { 835 return Objects.equals(mLastAdjacentTaskFragment, fragmentToken) 836 && Objects.equals(mLastAdjacentParams, params); 837 } 838 839 /** 840 * Updates the last requested adjacent TaskFragment token and params. 841 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS 842 */ setLastAdjacentTaskFragment(@onNull IBinder fragmentToken, @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params)843 void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken, 844 @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) { 845 mLastAdjacentTaskFragment = fragmentToken; 846 mLastAdjacentParams = params; 847 } 848 849 /** 850 * Clears the last requested adjacent TaskFragment token and params. 851 * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS 852 */ clearLastAdjacentTaskFragment()853 void clearLastAdjacentTaskFragment() { 854 final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null 855 ? mController.getContainer(mLastAdjacentTaskFragment) 856 : null; 857 mLastAdjacentTaskFragment = null; 858 mLastAdjacentParams = null; 859 if (lastAdjacentTaskFragment != null) { 860 // Clear the previous adjacent TaskFragment as well. 861 lastAdjacentTaskFragment.clearLastAdjacentTaskFragment(); 862 } 863 } 864 865 /** 866 * Checks if last requested companion TaskFragment token is equal to the provided value. 867 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT 868 */ isLastCompanionTaskFragmentEqual(@ullable IBinder fragmentToken)869 boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) { 870 return Objects.equals(mLastCompanionTaskFragment, fragmentToken); 871 } 872 873 /** 874 * Updates the last requested companion TaskFragment token. 875 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT 876 */ setLastCompanionTaskFragment(@ullable IBinder fragmentToken)877 void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) { 878 mLastCompanionTaskFragment = fragmentToken; 879 } 880 881 /** Returns whether to enable isolated navigation or not. */ isIsolatedNavigationEnabled()882 boolean isIsolatedNavigationEnabled() { 883 return mIsIsolatedNavigationEnabled; 884 } 885 886 /** Sets whether to enable isolated navigation or not. */ setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled)887 void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) { 888 mIsIsolatedNavigationEnabled = isolatedNavigationEnabled; 889 } 890 891 /** 892 * Returns whether this container is pinned. 893 * 894 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED 895 */ isPinned()896 boolean isPinned() { 897 return mIsPinned; 898 } 899 900 /** 901 * Sets whether to pin this container or not. 902 * 903 * @see #isPinned() 904 */ setPinned(boolean pinned)905 void setPinned(boolean pinned) { 906 mIsPinned = pinned; 907 } 908 909 /** 910 * Indicates to skip activity resolving if the activity is from this container. 911 * 912 * @see #isIsolatedNavigationEnabled() 913 * @see #isPinned() 914 */ shouldSkipActivityResolving()915 boolean shouldSkipActivityResolving() { 916 return isIsolatedNavigationEnabled() || isPinned(); 917 } 918 919 /** Sets whether to apply dim on the parent Task. */ setLastDimOnTask(boolean lastDimOnTask)920 void setLastDimOnTask(boolean lastDimOnTask) { 921 mLastDimOnTask = lastDimOnTask; 922 } 923 924 /** Returns whether to apply dim on the parent Task. */ isLastDimOnTask()925 boolean isLastDimOnTask() { 926 return mLastDimOnTask; 927 } 928 929 /** 930 * Adds the pending appeared activity that has requested to be launched in this task fragment. 931 * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment 932 */ addPendingAppearedInRequestedTaskFragmentActivity(Activity activity)933 void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) { 934 final IBinder activityToken = activity.getActivityToken(); 935 if (hasActivity(activityToken)) { 936 return; 937 } 938 mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken()); 939 } 940 941 /** 942 * Checks if the given activity has requested to be launched in this task fragment. 943 * @see #addPendingAppearedInRequestedTaskFragmentActivity 944 */ isActivityInRequestedTaskFragment(IBinder activityToken)945 boolean isActivityInRequestedTaskFragment(IBinder activityToken) { 946 if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) { 947 return true; 948 } 949 return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken); 950 } 951 952 /** Whether contains activities of another process */ hasCrossProcessActivities()953 boolean hasCrossProcessActivities() { 954 return mHasCrossProcessActivities; 955 } 956 957 /** Gets the parent leaf Task id. */ getTaskId()958 int getTaskId() { 959 return mTaskContainer.getTaskId(); 960 } 961 962 /** Gets the parent Task. */ 963 @NonNull getTaskContainer()964 TaskContainer getTaskContainer() { 965 return mTaskContainer; 966 } 967 968 @Nullable getMinDimensions()969 Size getMinDimensions() { 970 if (mInfo == null) { 971 return null; 972 } 973 int maxMinWidth = mInfo.getMinimumWidth(); 974 int maxMinHeight = mInfo.getMinimumHeight(); 975 for (IBinder activityToken : mPendingAppearedActivities) { 976 final Activity activity = mController.getActivity(activityToken); 977 final Size minDimensions = SplitPresenter.getMinDimensions(activity); 978 if (minDimensions == null) { 979 continue; 980 } 981 maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); 982 maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); 983 } 984 if (mPendingAppearedIntent != null) { 985 final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent); 986 if (minDimensions != null) { 987 maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); 988 maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); 989 } 990 } 991 return new Size(maxMinWidth, maxMinHeight); 992 } 993 994 /** Whether the current TaskFragment is above the {@code other} TaskFragment. */ isAbove(@onNull TaskFragmentContainer other)995 boolean isAbove(@NonNull TaskFragmentContainer other) { 996 if (mTaskContainer != other.mTaskContainer) { 997 throw new IllegalArgumentException( 998 "Trying to compare two TaskFragments in different Task."); 999 } 1000 if (this == other) { 1001 throw new IllegalArgumentException("Trying to compare a TaskFragment with itself."); 1002 } 1003 return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other); 1004 } 1005 1006 /** Returns whether this taskFragment container is an overlay container. */ isOverlay()1007 boolean isOverlay() { 1008 return mOverlayTag != null; 1009 } 1010 1011 /** 1012 * Returns the tag specified in launch options. {@code null} if this taskFragment container is 1013 * not an overlay container. 1014 */ 1015 @Nullable getOverlayTag()1016 String getOverlayTag() { 1017 return mOverlayTag; 1018 } 1019 1020 /** 1021 * Returns the options that was used to launch this {@link TaskFragmentContainer}. 1022 * {@link Bundle#isEmpty()} means there's no launch option for this container. 1023 * <p> 1024 * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object. 1025 */ 1026 @NonNull getLaunchOptions()1027 Bundle getLaunchOptions() { 1028 return mLaunchOptions; 1029 } 1030 1031 /** 1032 * Returns the associated Activity token of this overlay container. It must be {@code null} 1033 * for non-overlay container. 1034 * <p> 1035 * If an overlay container is associated with an activity, this overlay container will be 1036 * dismissed when the associated activity is destroyed. If the overlay container is visible, 1037 * activity will be launched on top of the overlay container and expanded to fill the parent 1038 * container. 1039 */ 1040 @Nullable getAssociatedActivityToken()1041 IBinder getAssociatedActivityToken() { 1042 return mAssociatedActivityToken; 1043 } 1044 1045 /** 1046 * Returns {@code true} if the overlay container should be always on top, which should be 1047 * a non-fill-parent overlay without activity association. 1048 */ isAlwaysOnTopOverlay()1049 boolean isAlwaysOnTopOverlay() { 1050 return isOverlay() && mAssociatedActivityToken == null; 1051 } 1052 isOverlayWithActivityAssociation()1053 boolean isOverlayWithActivityAssociation() { 1054 return isOverlay() && mAssociatedActivityToken != null; 1055 } 1056 1057 @Override toString()1058 public String toString() { 1059 return toString(true /* includeContainersToFinishOnExit */); 1060 } 1061 1062 /** 1063 * @return string for this TaskFragmentContainer and includes containers to finish on exit 1064 * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always 1065 * included in the string, then calling {@link #toString()} on a container that mutually 1066 * finishes with another container would cause a stack overflow. 1067 */ toString(boolean includeContainersToFinishOnExit)1068 private String toString(boolean includeContainersToFinishOnExit) { 1069 return "TaskFragmentContainer{" 1070 + " parentTaskId=" + getTaskId() 1071 + " token=" + mToken 1072 + " topNonFinishingActivity=" + getTopNonFinishingActivity() 1073 + " runningActivityCount=" + getRunningActivityCount() 1074 + " isFinished=" + mIsFinished 1075 + " overlayTag=" + mOverlayTag 1076 + " associatedActivityToken=" + mAssociatedActivityToken 1077 + " lastRequestedBounds=" + mLastRequestedBounds 1078 + " pendingAppearedActivities=" + mPendingAppearedActivities 1079 + (includeContainersToFinishOnExit ? " containersToFinishOnExit=" 1080 + containersToFinishOnExitToString() : "") 1081 + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit 1082 + " info=" + mInfo 1083 + "}"; 1084 } 1085 containersToFinishOnExitToString()1086 private String containersToFinishOnExitToString() { 1087 StringBuilder sb = new StringBuilder("["); 1088 Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator(); 1089 while (containerIterator.hasNext()) { 1090 sb.append(containerIterator.next().toString( 1091 false /* includeContainersToFinishOnExit */)); 1092 if (containerIterator.hasNext()) { 1093 sb.append(", "); 1094 } 1095 } 1096 return sb.append("]").toString(); 1097 } 1098 1099 static final class Builder { 1100 @NonNull 1101 private final SplitController mSplitController; 1102 1103 // The parent Task id of the new TaskFragment. 1104 private final int mTaskId; 1105 1106 // The activity in the same Task so that we can get the Task bounds if needed. 1107 @NonNull 1108 private final Activity mActivityInTask; 1109 1110 // The activity that will be reparented to the TaskFragment. 1111 @Nullable 1112 private Activity mPendingAppearedActivity; 1113 1114 // The Intent that will be started in the TaskFragment. 1115 @Nullable 1116 private Intent mPendingAppearedIntent; 1117 1118 // The paired primary {@link TaskFragmentContainer}. When it is set, the new container 1119 // will be added right above it. 1120 @Nullable 1121 private TaskFragmentContainer mPairedPrimaryContainer; 1122 1123 // The launch options bundle to create a container. Must be specified for overlay container. 1124 @Nullable 1125 private Bundle mLaunchOptions; 1126 1127 // The tag for the new created overlay container. This is required when creating an 1128 // overlay container. 1129 @Nullable 1130 private String mOverlayTag; 1131 1132 // The associated activity of the overlay container. Must be {@code null} for a 1133 // non-overlay container. 1134 @Nullable 1135 private Activity mAssociatedActivity; 1136 Builder(@onNull SplitController splitController, int taskId, @Nullable Activity activityInTask)1137 Builder(@NonNull SplitController splitController, int taskId, 1138 @Nullable Activity activityInTask) { 1139 if (taskId <= 0) { 1140 throw new IllegalArgumentException("taskId is invalid, " + taskId); 1141 } 1142 1143 mSplitController = splitController; 1144 mTaskId = taskId; 1145 mActivityInTask = activityInTask; 1146 } 1147 1148 @NonNull setPendingAppearedActivity(@ullable Activity pendingAppearedActivity)1149 Builder setPendingAppearedActivity(@Nullable Activity pendingAppearedActivity) { 1150 mPendingAppearedActivity = pendingAppearedActivity; 1151 return this; 1152 } 1153 1154 @NonNull setPendingAppearedIntent(@ullable Intent pendingAppearedIntent)1155 Builder setPendingAppearedIntent(@Nullable Intent pendingAppearedIntent) { 1156 mPendingAppearedIntent = pendingAppearedIntent; 1157 return this; 1158 } 1159 1160 @NonNull setPairedPrimaryContainer(@ullable TaskFragmentContainer pairedPrimaryContainer)1161 Builder setPairedPrimaryContainer(@Nullable TaskFragmentContainer pairedPrimaryContainer) { 1162 mPairedPrimaryContainer = pairedPrimaryContainer; 1163 return this; 1164 } 1165 1166 @NonNull setLaunchOptions(@ullable Bundle launchOptions)1167 Builder setLaunchOptions(@Nullable Bundle launchOptions) { 1168 mLaunchOptions = launchOptions; 1169 return this; 1170 } 1171 1172 @NonNull setOverlayTag(@ullable String overlayTag)1173 Builder setOverlayTag(@Nullable String overlayTag) { 1174 mOverlayTag = overlayTag; 1175 return this; 1176 } 1177 1178 @NonNull setAssociatedActivity(@ullable Activity associatedActivity)1179 Builder setAssociatedActivity(@Nullable Activity associatedActivity) { 1180 mAssociatedActivity = associatedActivity; 1181 return this; 1182 } 1183 1184 @NonNull build()1185 TaskFragmentContainer build() { 1186 if (mOverlayTag != null) { 1187 Objects.requireNonNull(mLaunchOptions); 1188 } else if (mAssociatedActivity != null) { 1189 throw new IllegalArgumentException("Associated activity must be null for " 1190 + "non-overlay activity."); 1191 } 1192 1193 TaskContainer taskContainer = mSplitController.getTaskContainer(mTaskId); 1194 if (taskContainer == null && mActivityInTask == null) { 1195 throw new IllegalArgumentException("mActivityInTask must be set."); 1196 } 1197 1198 if (taskContainer == null) { 1199 // Adding a TaskContainer if no existed one. 1200 taskContainer = new TaskContainer(mTaskId, mActivityInTask); 1201 mSplitController.addTaskContainer(mTaskId, taskContainer); 1202 } 1203 1204 return new TaskFragmentContainer(mPendingAppearedActivity, mPendingAppearedIntent, 1205 taskContainer, mSplitController, mPairedPrimaryContainer, mOverlayTag, 1206 mLaunchOptions, mAssociatedActivity); 1207 } 1208 } 1209 1210 static class OverlayContainerRestoreParams { 1211 /** The token of the overlay container */ 1212 @NonNull 1213 final IBinder mOverlayToken; 1214 1215 /** The launch options to create this container. */ 1216 @NonNull 1217 final Bundle mOptions; 1218 1219 /** The Intent that used to be started in the overlay container. */ 1220 @NonNull 1221 final Intent mIntent; 1222 OverlayContainerRestoreParams(@onNull IBinder overlayToken, @NonNull Bundle options, @NonNull Intent intent)1223 OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options, 1224 @NonNull Intent intent) { 1225 mOverlayToken = overlayToken; 1226 mOptions = options; 1227 mIntent = intent; 1228 } 1229 } 1230 } 1231