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.IBinder; 28 import android.util.Size; 29 import android.window.TaskFragmentAnimationParams; 30 import android.window.TaskFragmentInfo; 31 import android.window.WindowContainerTransaction; 32 33 import androidx.annotation.GuardedBy; 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.Iterator; 42 import java.util.List; 43 import java.util.Objects; 44 45 /** 46 * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment 47 * on the server side. 48 */ 49 // Suppress GuardedBy warning because all the TaskFragmentContainers are stored in 50 // SplitController.mTaskContainers which is guarded. 51 @SuppressWarnings("GuardedBy") 52 class TaskFragmentContainer { 53 private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000; 54 55 @NonNull 56 private final SplitController mController; 57 58 /** 59 * Client-created token that uniquely identifies the task fragment container instance. 60 */ 61 @NonNull 62 private final IBinder mToken; 63 64 /** Parent leaf Task. */ 65 @NonNull 66 private final TaskContainer mTaskContainer; 67 68 /** 69 * Server-provided task fragment information. 70 */ 71 @VisibleForTesting 72 TaskFragmentInfo mInfo; 73 74 /** 75 * Activity tokens that are being reparented or being started to this container, but haven't 76 * been added to {@link #mInfo} yet. 77 */ 78 @VisibleForTesting 79 final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>(); 80 81 /** 82 * When this container is created for an {@link Intent} to start within, we store that Intent 83 * until the container becomes non-empty on the server side, so that we can use it to check 84 * rules associated with this container. 85 */ 86 @Nullable 87 private Intent mPendingAppearedIntent; 88 89 /** 90 * The activities that were explicitly requested to be launched in its current TaskFragment, 91 * but haven't been added to {@link #mInfo} yet. 92 */ 93 final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>(); 94 95 /** Containers that are dependent on this one and should be completely destroyed on exit. */ 96 private final List<TaskFragmentContainer> mContainersToFinishOnExit = 97 new ArrayList<>(); 98 99 /** 100 * Individual associated activity tokens in different containers that should be finished on 101 * exit. 102 */ 103 private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>(); 104 105 /** Indicates whether the container was cleaned up after the last activity was removed. */ 106 private boolean mIsFinished; 107 108 /** 109 * Bounds that were requested last via {@link android.window.WindowContainerTransaction}. 110 */ 111 private final Rect mLastRequestedBounds = new Rect(); 112 113 /** 114 * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}. 115 */ 116 @WindowingMode 117 private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED; 118 119 /** 120 * TaskFragmentAnimationParams that was requested last via 121 * {@link android.window.WindowContainerTransaction}. 122 */ 123 @NonNull 124 private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT; 125 126 /** 127 * TaskFragment token that was requested last via 128 * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}. 129 */ 130 @Nullable 131 private IBinder mLastAdjacentTaskFragment; 132 133 /** 134 * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last 135 * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}. 136 */ 137 @Nullable 138 private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams; 139 140 /** 141 * TaskFragment token that was requested last via 142 * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}. 143 */ 144 @Nullable 145 private IBinder mLastCompanionTaskFragment; 146 147 /** 148 * When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment 149 * if it is still empty after the timeout. 150 */ 151 @VisibleForTesting 152 @Nullable 153 Runnable mAppearEmptyTimeout; 154 155 /** 156 * Whether this TaskFragment contains activities of another process/package. 157 */ 158 private boolean mHasCrossProcessActivities; 159 160 /** 161 * Creates a container with an existing activity that will be re-parented to it in a window 162 * container transaction. 163 * @param pairedPrimaryContainer when it is set, the new container will be add right above it 164 */ TaskFragmentContainer(@ullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer)165 TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, 166 @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, 167 @NonNull SplitController controller, 168 @Nullable TaskFragmentContainer pairedPrimaryContainer) { 169 if ((pendingAppearedActivity == null && pendingAppearedIntent == null) 170 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) { 171 throw new IllegalArgumentException( 172 "One and only one of pending activity and intent must be non-null"); 173 } 174 mController = controller; 175 mToken = new Binder("TaskFragmentContainer"); 176 mTaskContainer = taskContainer; 177 if (pairedPrimaryContainer != null) { 178 // The TaskFragment will be positioned right above the paired container. 179 if (pairedPrimaryContainer.getTaskContainer() != taskContainer) { 180 throw new IllegalArgumentException( 181 "pairedPrimaryContainer must be in the same Task"); 182 } 183 final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer); 184 taskContainer.addTaskFragmentContainer(primaryIndex + 1, this); 185 } else if (pendingAppearedActivity != null) { 186 // The TaskFragment will be positioned right above the pending appeared Activity. If any 187 // existing TaskFragment is empty with pending Intent, it is likely that the Activity of 188 // the pending Intent hasn't been created yet, so the new Activity should be below the 189 // empty TaskFragment. 190 final List<TaskFragmentContainer> containers = 191 taskContainer.getTaskFragmentContainers(); 192 int i = containers.size() - 1; 193 for (; i >= 0; i--) { 194 final TaskFragmentContainer container = containers.get(i); 195 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) { 196 break; 197 } 198 } 199 taskContainer.addTaskFragmentContainer(i + 1, this); 200 } else { 201 taskContainer.addTaskFragmentContainer(this); 202 } 203 if (pendingAppearedActivity != null) { 204 addPendingAppearedActivity(pendingAppearedActivity); 205 } 206 mPendingAppearedIntent = pendingAppearedIntent; 207 } 208 209 /** 210 * Returns the client-created token that uniquely identifies this container. 211 */ 212 @NonNull getTaskFragmentToken()213 IBinder getTaskFragmentToken() { 214 return mToken; 215 } 216 217 /** List of non-finishing activities that belong to this container and live in this process. */ 218 @NonNull collectNonFinishingActivities()219 List<Activity> collectNonFinishingActivities() { 220 final List<Activity> allActivities = new ArrayList<>(); 221 if (mInfo != null) { 222 // Add activities reported from the server. 223 for (IBinder token : mInfo.getActivities()) { 224 final Activity activity = mController.getActivity(token); 225 if (activity != null && !activity.isFinishing()) { 226 allActivities.add(activity); 227 } 228 } 229 } 230 231 // Add the re-parenting activity, in case the server has not yet reported the task 232 // fragment info update with it placed in this container. We still want to apply rules 233 // in this intermediate state. 234 // Place those on top of the list since they will be on the top after reported from the 235 // server. 236 for (IBinder token : mPendingAppearedActivities) { 237 final Activity activity = mController.getActivity(token); 238 if (activity != null && !activity.isFinishing()) { 239 allActivities.add(activity); 240 } 241 } 242 return allActivities; 243 } 244 245 /** Whether this TaskFragment is visible. */ isVisible()246 boolean isVisible() { 247 return mInfo != null && mInfo.isVisible(); 248 } 249 250 /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/ isInIntermediateState()251 boolean isInIntermediateState() { 252 if (mInfo == null) { 253 // Haven't received onTaskFragmentAppeared event. 254 return true; 255 } 256 if (mInfo.isEmpty()) { 257 // Empty TaskFragment will be removed or will have activity launched into it soon. 258 return true; 259 } 260 if (!mPendingAppearedActivities.isEmpty()) { 261 // Reparented activity hasn't appeared. 262 return true; 263 } 264 // Check if there is any reported activity that is no longer alive. 265 for (IBinder token : mInfo.getActivities()) { 266 final Activity activity = mController.getActivity(token); 267 if (activity == null && !mTaskContainer.isVisible()) { 268 // Activity can be null if the activity is not attached to process yet. That can 269 // happen when the activity is started in background. 270 continue; 271 } 272 if (activity == null || activity.isFinishing()) { 273 // One of the reported activity is no longer alive, wait for the server update. 274 return true; 275 } 276 } 277 return false; 278 } 279 280 @NonNull toActivityStack()281 ActivityStack toActivityStack() { 282 return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken); 283 } 284 285 /** Adds the activity that will be reparented to this container. */ addPendingAppearedActivity(@onNull Activity pendingAppearedActivity)286 void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { 287 final IBinder activityToken = pendingAppearedActivity.getActivityToken(); 288 if (hasActivity(activityToken)) { 289 return; 290 } 291 // Remove the pending activity from other TaskFragments in case the activity is reparented 292 // again before the server update. 293 mTaskContainer.cleanupPendingAppearedActivity(activityToken); 294 mPendingAppearedActivities.add(activityToken); 295 updateActivityClientRecordTaskFragmentToken(activityToken); 296 } 297 298 /** 299 * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the 300 * activity. This makes sure the token is up-to-date if the activity is relaunched later. 301 */ updateActivityClientRecordTaskFragmentToken(@onNull IBinder activityToken)302 private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) { 303 final ActivityThread.ActivityClientRecord record = ActivityThread 304 .currentActivityThread().getActivityClient(activityToken); 305 if (record != null) { 306 record.mTaskFragmentToken = mToken; 307 } 308 } 309 removePendingAppearedActivity(@onNull IBinder activityToken)310 void removePendingAppearedActivity(@NonNull IBinder activityToken) { 311 mPendingAppearedActivities.remove(activityToken); 312 // Also remove the activity from the mPendingInRequestedTaskFragmentActivities. 313 mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken); 314 } 315 316 @GuardedBy("mController.mLock") clearPendingAppearedActivities()317 void clearPendingAppearedActivities() { 318 final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities); 319 // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the 320 // current TaskFragment. 321 mPendingAppearedActivities.clear(); 322 mPendingAppearedIntent = null; 323 324 // For removed pending activities, we need to update the them to their previous containers. 325 for (IBinder activityToken : cleanupActivities) { 326 final TaskFragmentContainer curContainer = mController.getContainerWithActivity( 327 activityToken); 328 if (curContainer != null) { 329 curContainer.updateActivityClientRecordTaskFragmentToken(activityToken); 330 } 331 } 332 } 333 334 /** Called when the activity is destroyed. */ onActivityDestroyed(@onNull IBinder activityToken)335 void onActivityDestroyed(@NonNull IBinder activityToken) { 336 removePendingAppearedActivity(activityToken); 337 if (mInfo != null) { 338 // Remove the activity now because there can be a delay before the server callback. 339 mInfo.getActivities().remove(activityToken); 340 } 341 mActivitiesToFinishOnExit.remove(activityToken); 342 } 343 344 @Nullable getPendingAppearedIntent()345 Intent getPendingAppearedIntent() { 346 return mPendingAppearedIntent; 347 } 348 setPendingAppearedIntent(@ullable Intent intent)349 void setPendingAppearedIntent(@Nullable Intent intent) { 350 mPendingAppearedIntent = intent; 351 } 352 353 /** 354 * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the 355 * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has 356 * running activities). 357 */ clearPendingAppearedIntentIfNeeded(@onNull Intent intent)358 void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) { 359 if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) { 360 return; 361 } 362 mPendingAppearedIntent = null; 363 } 364 hasActivity(@onNull IBinder activityToken)365 boolean hasActivity(@NonNull IBinder activityToken) { 366 // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make 367 // sure the controller considers this container as the one containing the activity. 368 // This is needed when the activity is added as pending appeared activity to one 369 // TaskFragment while it is also an appeared activity in another. 370 return mController.getContainerWithActivity(activityToken) == this; 371 } 372 373 /** Whether this activity has appeared in the TaskFragment on the server side. */ hasAppearedActivity(@onNull IBinder activityToken)374 boolean hasAppearedActivity(@NonNull IBinder activityToken) { 375 return mInfo != null && mInfo.getActivities().contains(activityToken); 376 } 377 378 /** 379 * Whether we are waiting for this activity to appear in the TaskFragment on the server side. 380 */ hasPendingAppearedActivity(@onNull IBinder activityToken)381 boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) { 382 return mPendingAppearedActivities.contains(activityToken); 383 } 384 getRunningActivityCount()385 int getRunningActivityCount() { 386 int count = mPendingAppearedActivities.size(); 387 if (mInfo != null) { 388 count += mInfo.getRunningActivityCount(); 389 } 390 return count; 391 } 392 393 /** Whether we are waiting for the TaskFragment to appear and become non-empty. */ isWaitingActivityAppear()394 boolean isWaitingActivityAppear() { 395 return !mIsFinished && (mInfo == null || mAppearEmptyTimeout != null); 396 } 397 398 @Nullable getInfo()399 TaskFragmentInfo getInfo() { 400 return mInfo; 401 } 402 403 @GuardedBy("mController.mLock") setInfo(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info)404 void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) { 405 if (!mIsFinished && mInfo == null && info.isEmpty()) { 406 // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no 407 // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if 408 // it is still empty after timeout. 409 if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) { 410 mAppearEmptyTimeout = () -> { 411 synchronized (mController.mLock) { 412 mAppearEmptyTimeout = null; 413 // Call without the pass-in wct when timeout. We need to applyWct directly 414 // in this case. 415 mController.onTaskFragmentAppearEmptyTimeout(this); 416 } 417 }; 418 mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); 419 } else { 420 mAppearEmptyTimeout = null; 421 mController.onTaskFragmentAppearEmptyTimeout(wct, this); 422 } 423 } else if (mAppearEmptyTimeout != null && !info.isEmpty()) { 424 mController.getHandler().removeCallbacks(mAppearEmptyTimeout); 425 mAppearEmptyTimeout = null; 426 } 427 428 mHasCrossProcessActivities = false; 429 mInfo = info; 430 if (mInfo == null || mInfo.isEmpty()) { 431 return; 432 } 433 434 // Contains activities of another process if the activities size is not matched to the 435 // running activity count 436 if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) { 437 mHasCrossProcessActivities = true; 438 } 439 440 // Only track the pending Intent when the container is empty. 441 mPendingAppearedIntent = null; 442 if (mPendingAppearedActivities.isEmpty()) { 443 return; 444 } 445 // Cleanup activities that were being re-parented 446 List<IBinder> infoActivities = mInfo.getActivities(); 447 for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) { 448 final IBinder activityToken = mPendingAppearedActivities.get(i); 449 if (infoActivities.contains(activityToken)) { 450 removePendingAppearedActivity(activityToken); 451 } 452 } 453 } 454 455 @Nullable getTopNonFinishingActivity()456 Activity getTopNonFinishingActivity() { 457 final List<Activity> activities = collectNonFinishingActivities(); 458 return activities.isEmpty() ? null : activities.get(activities.size() - 1); 459 } 460 461 @Nullable getBottomMostActivity()462 Activity getBottomMostActivity() { 463 final List<Activity> activities = collectNonFinishingActivities(); 464 return activities.isEmpty() ? null : activities.get(0); 465 } 466 isEmpty()467 boolean isEmpty() { 468 return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty()); 469 } 470 471 /** 472 * Adds a container that should be finished when this container is finished. 473 */ addContainerToFinishOnExit(@onNull TaskFragmentContainer containerToFinish)474 void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) { 475 if (mIsFinished) { 476 return; 477 } 478 mContainersToFinishOnExit.add(containerToFinish); 479 } 480 481 /** 482 * Removes a container that should be finished when this container is finished. 483 */ removeContainerToFinishOnExit(@onNull TaskFragmentContainer containerToRemove)484 void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) { 485 removeContainersToFinishOnExit(Collections.singletonList(containerToRemove)); 486 } 487 488 /** 489 * Removes container list that should be finished when this container is finished. 490 */ removeContainersToFinishOnExit(@onNull List<TaskFragmentContainer> containersToRemove)491 void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) { 492 if (mIsFinished) { 493 return; 494 } 495 mContainersToFinishOnExit.removeAll(containersToRemove); 496 } 497 498 /** 499 * Adds an activity that should be finished when this container is finished. 500 */ addActivityToFinishOnExit(@onNull Activity activityToFinish)501 void addActivityToFinishOnExit(@NonNull Activity activityToFinish) { 502 if (mIsFinished) { 503 return; 504 } 505 mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken()); 506 } 507 508 /** 509 * Removes an activity that should be finished when this container is finished. 510 */ removeActivityToFinishOnExit(@onNull Activity activityToRemove)511 void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) { 512 if (mIsFinished) { 513 return; 514 } 515 mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken()); 516 } 517 518 /** Removes all dependencies that should be finished when this container is finished. */ resetDependencies()519 void resetDependencies() { 520 if (mIsFinished) { 521 return; 522 } 523 mContainersToFinishOnExit.clear(); 524 mActivitiesToFinishOnExit.clear(); 525 } 526 527 /** 528 * Removes all activities that belong to this process and finishes other containers/activities 529 * configured to finish together. 530 */ 531 @GuardedBy("mController.mLock") finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)532 void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, 533 @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { 534 finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */); 535 } 536 537 /** 538 * Removes all activities that belong to this process and finishes other containers/activities 539 * configured to finish together. 540 */ finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller, boolean shouldRemoveRecord)541 void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, 542 @NonNull WindowContainerTransaction wct, @NonNull SplitController controller, 543 boolean shouldRemoveRecord) { 544 if (!mIsFinished) { 545 mIsFinished = true; 546 if (mAppearEmptyTimeout != null) { 547 mController.getHandler().removeCallbacks(mAppearEmptyTimeout); 548 mAppearEmptyTimeout = null; 549 } 550 finishActivities(shouldFinishDependent, presenter, wct, controller); 551 } 552 553 if (mInfo == null) { 554 // Defer removal the container and wait until TaskFragment appeared. 555 return; 556 } 557 558 // Cleanup the visuals 559 presenter.deleteTaskFragment(wct, getTaskFragmentToken()); 560 if (shouldRemoveRecord) { 561 // Cleanup the records 562 controller.removeContainer(this); 563 } 564 // Clean up task fragment information 565 mInfo = null; 566 } 567 568 @GuardedBy("mController.mLock") finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller)569 private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, 570 @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { 571 // Finish own activities 572 for (Activity activity : collectNonFinishingActivities()) { 573 if (!activity.isFinishing() 574 // In case we have requested to reparent the activity to another container (as 575 // pendingAppeared), we don't want to finish it with this container. 576 && mController.getContainerWithActivity(activity) == this) { 577 wct.finishActivity(activity.getActivityToken()); 578 } 579 } 580 581 if (!shouldFinishDependent) { 582 // Always finish the placeholder when the primary is finished. 583 finishPlaceholderIfAny(wct, presenter); 584 return; 585 } 586 587 // Finish dependent containers 588 for (TaskFragmentContainer container : mContainersToFinishOnExit) { 589 if (container.mIsFinished 590 || controller.shouldRetainAssociatedContainer(this, container)) { 591 continue; 592 } 593 container.finish(true /* shouldFinishDependent */, presenter, 594 wct, controller); 595 } 596 mContainersToFinishOnExit.clear(); 597 598 // Finish associated activities 599 for (IBinder activityToken : mActivitiesToFinishOnExit) { 600 final Activity activity = mController.getActivity(activityToken); 601 if (activity == null || activity.isFinishing() 602 || controller.shouldRetainAssociatedActivity(this, activity)) { 603 continue; 604 } 605 wct.finishActivity(activity.getActivityToken()); 606 } 607 mActivitiesToFinishOnExit.clear(); 608 } 609 610 @GuardedBy("mController.mLock") finishPlaceholderIfAny(@onNull WindowContainerTransaction wct, @NonNull SplitPresenter presenter)611 private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct, 612 @NonNull SplitPresenter presenter) { 613 final List<TaskFragmentContainer> containersToRemove = new ArrayList<>(); 614 for (TaskFragmentContainer container : mContainersToFinishOnExit) { 615 if (container.mIsFinished) { 616 continue; 617 } 618 final SplitContainer splitContainer = mController.getActiveSplitForContainers( 619 this, container); 620 if (splitContainer != null && splitContainer.isPlaceholderContainer() 621 && splitContainer.getSecondaryContainer() == container) { 622 // Remove the placeholder secondary TaskFragment. 623 containersToRemove.add(container); 624 } 625 } 626 mContainersToFinishOnExit.removeAll(containersToRemove); 627 for (TaskFragmentContainer container : containersToRemove) { 628 container.finish(false /* shouldFinishDependent */, presenter, wct, mController); 629 } 630 } 631 isFinished()632 boolean isFinished() { 633 return mIsFinished; 634 } 635 636 /** 637 * Checks if last requested bounds are equal to the provided value. 638 * The requested bounds are relative bounds in parent coordinate. 639 * @see WindowContainerTransaction#setRelativeBounds 640 */ areLastRequestedBoundsEqual(@ullable Rect relBounds)641 boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) { 642 return (relBounds == null && mLastRequestedBounds.isEmpty()) 643 || mLastRequestedBounds.equals(relBounds); 644 } 645 646 /** 647 * Updates the last requested bounds. 648 * The requested bounds are relative bounds in parent coordinate. 649 * @see WindowContainerTransaction#setRelativeBounds 650 */ setLastRequestedBounds(@ullable Rect relBounds)651 void setLastRequestedBounds(@Nullable Rect relBounds) { 652 if (relBounds == null) { 653 mLastRequestedBounds.setEmpty(); 654 } else { 655 mLastRequestedBounds.set(relBounds); 656 } 657 } 658 659 /** 660 * Checks if last requested windowing mode is equal to the provided value. 661 * @see WindowContainerTransaction#setWindowingMode 662 */ isLastRequestedWindowingModeEqual(@indowingMode int windowingMode)663 boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) { 664 return mLastRequestedWindowingMode == windowingMode; 665 } 666 667 /** 668 * Updates the last requested windowing mode. 669 * @see WindowContainerTransaction#setWindowingMode 670 */ setLastRequestedWindowingMode(@indowingMode int windowingModes)671 void setLastRequestedWindowingMode(@WindowingMode int windowingModes) { 672 mLastRequestedWindowingMode = windowingModes; 673 } 674 675 /** 676 * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value. 677 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS 678 */ areLastRequestedAnimationParamsEqual( @onNull TaskFragmentAnimationParams animationParams)679 boolean areLastRequestedAnimationParamsEqual( 680 @NonNull TaskFragmentAnimationParams animationParams) { 681 return mLastAnimationParams.equals(animationParams); 682 } 683 684 /** 685 * Updates the last requested {@link TaskFragmentAnimationParams}. 686 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS 687 */ setLastRequestAnimationParams(@onNull TaskFragmentAnimationParams animationParams)688 void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) { 689 mLastAnimationParams = animationParams; 690 } 691 692 /** 693 * Checks if last requested adjacent TaskFragment token and params are equal to the provided 694 * values. 695 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS 696 * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS 697 */ isLastAdjacentTaskFragmentEqual(@ullable IBinder fragmentToken, @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params)698 boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken, 699 @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) { 700 return Objects.equals(mLastAdjacentTaskFragment, fragmentToken) 701 && Objects.equals(mLastAdjacentParams, params); 702 } 703 704 /** 705 * Updates the last requested adjacent TaskFragment token and params. 706 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS 707 */ setLastAdjacentTaskFragment(@onNull IBinder fragmentToken, @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params)708 void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken, 709 @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) { 710 mLastAdjacentTaskFragment = fragmentToken; 711 mLastAdjacentParams = params; 712 } 713 714 /** 715 * Clears the last requested adjacent TaskFragment token and params. 716 * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS 717 */ clearLastAdjacentTaskFragment()718 void clearLastAdjacentTaskFragment() { 719 final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null 720 ? mController.getContainer(mLastAdjacentTaskFragment) 721 : null; 722 mLastAdjacentTaskFragment = null; 723 mLastAdjacentParams = null; 724 if (lastAdjacentTaskFragment != null) { 725 // Clear the previous adjacent TaskFragment as well. 726 lastAdjacentTaskFragment.clearLastAdjacentTaskFragment(); 727 } 728 } 729 730 /** 731 * Checks if last requested companion TaskFragment token is equal to the provided value. 732 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT 733 */ isLastCompanionTaskFragmentEqual(@ullable IBinder fragmentToken)734 boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) { 735 return Objects.equals(mLastCompanionTaskFragment, fragmentToken); 736 } 737 738 /** 739 * Updates the last requested companion TaskFragment token. 740 * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT 741 */ setLastCompanionTaskFragment(@ullable IBinder fragmentToken)742 void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) { 743 mLastCompanionTaskFragment = fragmentToken; 744 } 745 746 /** 747 * Adds the pending appeared activity that has requested to be launched in this task fragment. 748 * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment 749 */ addPendingAppearedInRequestedTaskFragmentActivity(Activity activity)750 void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) { 751 final IBinder activityToken = activity.getActivityToken(); 752 if (hasActivity(activityToken)) { 753 return; 754 } 755 mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken()); 756 } 757 758 /** 759 * Checks if the given activity has requested to be launched in this task fragment. 760 * @see #addPendingAppearedInRequestedTaskFragmentActivity 761 */ isActivityInRequestedTaskFragment(IBinder activityToken)762 boolean isActivityInRequestedTaskFragment(IBinder activityToken) { 763 if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) { 764 return true; 765 } 766 return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken); 767 } 768 769 /** Whether contains activities of another process */ hasCrossProcessActivities()770 boolean hasCrossProcessActivities() { 771 return mHasCrossProcessActivities; 772 } 773 774 /** Gets the parent leaf Task id. */ getTaskId()775 int getTaskId() { 776 return mTaskContainer.getTaskId(); 777 } 778 779 /** Gets the parent Task. */ 780 @NonNull getTaskContainer()781 TaskContainer getTaskContainer() { 782 return mTaskContainer; 783 } 784 785 @Nullable getMinDimensions()786 Size getMinDimensions() { 787 if (mInfo == null) { 788 return null; 789 } 790 int maxMinWidth = mInfo.getMinimumWidth(); 791 int maxMinHeight = mInfo.getMinimumHeight(); 792 for (IBinder activityToken : mPendingAppearedActivities) { 793 final Activity activity = mController.getActivity(activityToken); 794 final Size minDimensions = SplitPresenter.getMinDimensions(activity); 795 if (minDimensions == null) { 796 continue; 797 } 798 maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); 799 maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); 800 } 801 if (mPendingAppearedIntent != null) { 802 final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent); 803 if (minDimensions != null) { 804 maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); 805 maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); 806 } 807 } 808 return new Size(maxMinWidth, maxMinHeight); 809 } 810 811 /** Whether the current TaskFragment is above the {@code other} TaskFragment. */ isAbove(@onNull TaskFragmentContainer other)812 boolean isAbove(@NonNull TaskFragmentContainer other) { 813 if (mTaskContainer != other.mTaskContainer) { 814 throw new IllegalArgumentException( 815 "Trying to compare two TaskFragments in different Task."); 816 } 817 if (this == other) { 818 throw new IllegalArgumentException("Trying to compare a TaskFragment with itself."); 819 } 820 return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other); 821 } 822 823 @Override toString()824 public String toString() { 825 return toString(true /* includeContainersToFinishOnExit */); 826 } 827 828 /** 829 * @return string for this TaskFragmentContainer and includes containers to finish on exit 830 * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always 831 * included in the string, then calling {@link #toString()} on a container that mutually 832 * finishes with another container would cause a stack overflow. 833 */ toString(boolean includeContainersToFinishOnExit)834 private String toString(boolean includeContainersToFinishOnExit) { 835 return "TaskFragmentContainer{" 836 + " parentTaskId=" + getTaskId() 837 + " token=" + mToken 838 + " topNonFinishingActivity=" + getTopNonFinishingActivity() 839 + " runningActivityCount=" + getRunningActivityCount() 840 + " isFinished=" + mIsFinished 841 + " lastRequestedBounds=" + mLastRequestedBounds 842 + " pendingAppearedActivities=" + mPendingAppearedActivities 843 + (includeContainersToFinishOnExit ? " containersToFinishOnExit=" 844 + containersToFinishOnExitToString() : "") 845 + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit 846 + " info=" + mInfo 847 + "}"; 848 } 849 containersToFinishOnExitToString()850 private String containersToFinishOnExitToString() { 851 StringBuilder sb = new StringBuilder("["); 852 Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator(); 853 while (containerIterator.hasNext()) { 854 sb.append(containerIterator.next().toString( 855 false /* includeContainersToFinishOnExit */)); 856 if (containerIterator.hasNext()) { 857 sb.append(", "); 858 } 859 } 860 return sb.append("]").toString(); 861 } 862 } 863