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