1 /* 2 * Copyright (C) 2022 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.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 23 import static android.app.WindowConfiguration.inMultiWindowMode; 24 25 import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_PRIMARY; 26 import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_SECONDARY; 27 28 import android.app.Activity; 29 import android.app.ActivityClient; 30 import android.app.WindowConfiguration; 31 import android.app.WindowConfiguration.WindowingMode; 32 import android.content.res.Configuration; 33 import android.graphics.Rect; 34 import android.os.IBinder; 35 import android.util.ArraySet; 36 import android.util.DisplayMetrics; 37 import android.util.Log; 38 import android.view.WindowInsets; 39 import android.view.WindowMetrics; 40 import android.window.TaskFragmentInfo; 41 import android.window.TaskFragmentParentInfo; 42 import android.window.WindowContainerTransaction; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.window.extensions.core.util.function.Predicate; 47 import androidx.window.extensions.embedding.SplitAttributes.SplitType; 48 import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType; 49 import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Set; 54 55 /** Represents TaskFragments and split pairs below a Task. */ 56 class TaskContainer { 57 private static final String TAG = TaskContainer.class.getSimpleName(); 58 59 /** The unique task id. */ 60 private final int mTaskId; 61 62 /** Active TaskFragments in this Task. */ 63 @NonNull 64 private final List<TaskFragmentContainer> mContainers = new ArrayList<>(); 65 66 /** Active split pairs in this Task. */ 67 @NonNull 68 private final List<SplitContainer> mSplitContainers = new ArrayList<>(); 69 70 /** Active pin split pair in this Task. */ 71 @Nullable 72 private SplitPinContainer mSplitPinContainer; 73 74 /** 75 * The {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the Task. 76 */ 77 @Nullable 78 private TaskFragmentContainer mAlwaysOnTopOverlayContainer; 79 80 @NonNull 81 private TaskFragmentParentInfo mInfo; 82 83 /** 84 * TaskFragments that the organizer has requested to be closed. They should be removed when 85 * the organizer receives 86 * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)} 87 * event for them. 88 */ 89 final Set<IBinder> mFinishedContainer = new ArraySet<>(); 90 91 /** 92 * The {@link RatioSplitType} that will be applied to newly added containers. This is to ensure 93 * the required UX that, after user dragging the divider, the split ratio is persistent after 94 * launching a new activity into a new TaskFragment in the same Task. 95 */ 96 private RatioSplitType mOverrideSplitType; 97 98 /** 99 * If {@code true}, suppress the placeholder rules in the {@link TaskContainer}. 100 * <p> 101 * This is used in case the user drags the divider to fully expand the primary container and 102 * dismiss the secondary container while a {@link SplitPlaceholderRule} is used. Without this 103 * flag, after dismissing the secondary container, a placeholder will be launched again. 104 * <p> 105 * This flag is set true when the primary container is fully expanded and cleared when a new 106 * split is added to the {@link TaskContainer}. 107 */ 108 private boolean mPlaceholderRuleSuppressed; 109 110 /** 111 * {@code true} if the TaskFragments in this Task needs to be updated next time the Task 112 * becomes visible. See {@link #shouldUpdateContainer(TaskFragmentParentInfo)} 113 */ 114 boolean mTaskFragmentContainersNeedsUpdate; 115 116 /** 117 * The {@link TaskContainer} constructor 118 * 119 * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with 120 * {@code activityInTask}. 121 * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to 122 * initialize the {@link TaskContainer} properties. 123 */ TaskContainer(int taskId, @NonNull Activity activityInTask)124 TaskContainer(int taskId, @NonNull Activity activityInTask) { 125 if (taskId == INVALID_TASK_ID) { 126 throw new IllegalArgumentException("Invalid Task id"); 127 } 128 mTaskId = taskId; 129 final TaskProperties taskProperties = TaskProperties 130 .getTaskPropertiesFromActivity(activityInTask); 131 mInfo = new TaskFragmentParentInfo( 132 taskProperties.getConfiguration(), 133 taskProperties.getDisplayId(), 134 // Note that it is always called when there's a new Activity is started, which 135 // implies the host task is visible and has an activity in the task. 136 true /* visible */, 137 true /* hasDirectActivity */, 138 null /* decorSurface */); 139 } 140 getTaskId()141 int getTaskId() { 142 return mTaskId; 143 } 144 getDisplayId()145 int getDisplayId() { 146 return mInfo.getDisplayId(); 147 } 148 isVisible()149 boolean isVisible() { 150 return mInfo.isVisible(); 151 } 152 setInvisible()153 void setInvisible() { 154 mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(), 155 false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface()); 156 } 157 hasDirectActivity()158 boolean hasDirectActivity() { 159 return mInfo.hasDirectActivity(); 160 } 161 162 @NonNull getBounds()163 Rect getBounds() { 164 return mInfo.getConfiguration().windowConfiguration.getBounds(); 165 } 166 167 @NonNull getTaskProperties()168 TaskProperties getTaskProperties() { 169 return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration()); 170 } 171 updateTaskFragmentParentInfo(@onNull TaskFragmentParentInfo info)172 void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { 173 mInfo = info; 174 } 175 176 @NonNull getTaskFragmentParentInfo()177 TaskFragmentParentInfo getTaskFragmentParentInfo() { 178 return mInfo; 179 } 180 181 /** 182 * Returns {@code true} if the container should be updated with {@code info}. 183 */ shouldUpdateContainer(@onNull TaskFragmentParentInfo info)184 boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) { 185 final Configuration configuration = info.getConfiguration(); 186 187 if (isInPictureInPicture(configuration)) { 188 // No need to update presentation in PIP until the Task exit PIP. 189 return false; 190 } 191 192 // If the task properties equals regardless of starting position, don't 193 // need to update the container. 194 return mTaskFragmentContainersNeedsUpdate 195 || mInfo.getConfiguration().diffPublicOnly(configuration) != 0 196 || mInfo.getDisplayId() != info.getDisplayId(); 197 } 198 199 /** 200 * Returns the windowing mode for the TaskFragments below this Task, which should be split with 201 * other TaskFragments. 202 * 203 * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when 204 * the pair of TaskFragments are stacked due to the limited space. 205 */ 206 @WindowingMode getWindowingModeForTaskFragment(@ullable Rect taskFragmentBounds)207 int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) { 208 // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it 209 // will be set to UNDEFINED which will then inherit the Task windowing mode. 210 if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) { 211 return WINDOWING_MODE_UNDEFINED; 212 } 213 // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. 214 // However, when the Task is in other multi windowing mode, such as Freeform, we need to 215 // have the activity windowing mode to match the Task, otherwise things like 216 // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the 217 // Task windowing mode if the Task is in multi window. 218 // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. 219 return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW; 220 } 221 isInPictureInPicture()222 boolean isInPictureInPicture() { 223 return isInPictureInPicture(mInfo.getConfiguration()); 224 } 225 isInPictureInPicture(@onNull Configuration configuration)226 private static boolean isInPictureInPicture(@NonNull Configuration configuration) { 227 return configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; 228 } 229 isInMultiWindow()230 boolean isInMultiWindow() { 231 return WindowConfiguration.inMultiWindowMode(getWindowingMode()); 232 } 233 234 @WindowingMode getWindowingMode()235 private int getWindowingMode() { 236 return mInfo.getConfiguration().windowConfiguration.getWindowingMode(); 237 } 238 239 /** Whether there is any {@link TaskFragmentContainer} below this Task. */ isEmpty()240 boolean isEmpty() { 241 return mContainers.isEmpty() && mFinishedContainer.isEmpty(); 242 } 243 244 /** Called when the activity {@link Activity#isFinishing()} and paused. */ onFinishingActivityPaused(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)245 void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct, 246 @NonNull IBinder activityToken) { 247 for (TaskFragmentContainer container : mContainers) { 248 container.onFinishingActivityPaused(wct, activityToken); 249 } 250 } 251 252 /** Called when the activity is destroyed. */ onActivityDestroyed(@onNull WindowContainerTransaction wct, @NonNull IBinder activityToken)253 void onActivityDestroyed(@NonNull WindowContainerTransaction wct, 254 @NonNull IBinder activityToken) { 255 for (TaskFragmentContainer container : mContainers) { 256 container.onActivityDestroyed(wct, activityToken); 257 } 258 } 259 260 /** Removes the pending appeared activity from all TaskFragments in this Task. */ cleanupPendingAppearedActivity(@onNull IBinder activityToken)261 void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) { 262 for (TaskFragmentContainer container : mContainers) { 263 container.removePendingAppearedActivity(activityToken); 264 } 265 } 266 267 @Nullable getTopNonFinishingTaskFragmentContainer()268 TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() { 269 return getTopNonFinishingTaskFragmentContainer(true /* includePin */); 270 } 271 272 @Nullable getTopNonFinishingTaskFragmentContainer(boolean includePin)273 TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) { 274 return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */); 275 } 276 277 @Nullable getTopNonFinishingTaskFragmentContainer(boolean includePin, boolean includeOverlay)278 TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin, 279 boolean includeOverlay) { 280 for (int i = mContainers.size() - 1; i >= 0; i--) { 281 final TaskFragmentContainer container = mContainers.get(i); 282 if (!includePin && isTaskFragmentContainerPinned(container)) { 283 continue; 284 } 285 if (!includeOverlay && container.isOverlay()) { 286 continue; 287 } 288 if (!container.isFinished()) { 289 return container; 290 } 291 } 292 return null; 293 } 294 295 /** Gets a non-finishing container below the given one. */ 296 @Nullable getNonFinishingTaskFragmentContainerBelow( @onNull TaskFragmentContainer current)297 TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow( 298 @NonNull TaskFragmentContainer current) { 299 final int index = mContainers.indexOf(current); 300 for (int i = index - 1; i >= 0; i--) { 301 final TaskFragmentContainer container = mContainers.get(i); 302 if (!container.isFinished()) { 303 return container; 304 } 305 } 306 return null; 307 } 308 309 @Nullable getTopNonFinishingActivity(boolean includeOverlay)310 Activity getTopNonFinishingActivity(boolean includeOverlay) { 311 for (int i = mContainers.size() - 1; i >= 0; i--) { 312 final TaskFragmentContainer container = mContainers.get(i); 313 if (!includeOverlay && container.isOverlay()) { 314 continue; 315 } 316 final Activity activity = container.getTopNonFinishingActivity(); 317 if (activity != null) { 318 return activity; 319 } 320 } 321 return null; 322 } 323 324 @Nullable getContainerWithActivity(@onNull IBinder activityToken)325 TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { 326 return getContainer(container -> container.hasAppearedActivity(activityToken) 327 || container.hasPendingAppearedActivity(activityToken)); 328 } 329 330 @Nullable getContainer(@onNull Predicate<TaskFragmentContainer> predicate)331 TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) { 332 for (int i = mContainers.size() - 1; i >= 0; i--) { 333 final TaskFragmentContainer container = mContainers.get(i); 334 if (predicate.test(container)) { 335 return container; 336 } 337 } 338 return null; 339 } 340 341 @Nullable getActiveSplitForContainer(@ullable TaskFragmentContainer container)342 SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) { 343 if (container == null) { 344 return null; 345 } 346 for (int i = mSplitContainers.size() - 1; i >= 0; i--) { 347 final SplitContainer splitContainer = mSplitContainers.get(i); 348 if (container.equals(splitContainer.getSecondaryContainer()) 349 || container.equals(splitContainer.getPrimaryContainer())) { 350 return splitContainer; 351 } 352 } 353 return null; 354 } 355 356 /** 357 * Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist. 358 */ 359 @Nullable getAlwaysOnTopOverlayContainer()360 TaskFragmentContainer getAlwaysOnTopOverlayContainer() { 361 return mAlwaysOnTopOverlayContainer; 362 } 363 indexOf(@onNull TaskFragmentContainer child)364 int indexOf(@NonNull TaskFragmentContainer child) { 365 return mContainers.indexOf(child); 366 } 367 368 /** Whether the Task is in an intermediate state waiting for the server update. */ isInIntermediateState()369 boolean isInIntermediateState() { 370 for (TaskFragmentContainer container : mContainers) { 371 if (container.isInIntermediateState()) { 372 // We are in an intermediate state to wait for server update on this TaskFragment. 373 return true; 374 } 375 } 376 return false; 377 } 378 379 /** 380 * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the 381 * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead. 382 */ 383 @NonNull getSplitContainers()384 List<SplitContainer> getSplitContainers() { 385 return mSplitContainers; 386 } 387 addSplitContainer(@onNull SplitContainer splitContainer)388 void addSplitContainer(@NonNull SplitContainer splitContainer) { 389 // Reset the placeholder rule suppression when a new split container is added. 390 mPlaceholderRuleSuppressed = false; 391 392 applyOverrideSplitTypeIfNeeded(splitContainer); 393 394 if (splitContainer instanceof SplitPinContainer) { 395 mSplitPinContainer = (SplitPinContainer) splitContainer; 396 mSplitContainers.add(splitContainer); 397 return; 398 } 399 400 // Keeps the SplitPinContainer on the top of the list. 401 mSplitContainers.remove(mSplitPinContainer); 402 mSplitContainers.add(splitContainer); 403 if (mSplitPinContainer != null) { 404 mSplitContainers.add(mSplitPinContainer); 405 } 406 } 407 isPlaceholderRuleSuppressed()408 boolean isPlaceholderRuleSuppressed() { 409 return mPlaceholderRuleSuppressed; 410 } 411 412 // If there is an override SplitType due to user dragging the divider, the split ratio should 413 // be applied to newly added SplitContainers. applyOverrideSplitTypeIfNeeded(@onNull SplitContainer splitContainer)414 private void applyOverrideSplitTypeIfNeeded(@NonNull SplitContainer splitContainer) { 415 if (mOverrideSplitType == null) { 416 return; 417 } 418 final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes(); 419 final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes(); 420 if (!(splitAttributes.getSplitType() instanceof RatioSplitType)) { 421 // Skip if the original split type is not a ratio type. 422 return; 423 } 424 if (dividerAttributes == null 425 || dividerAttributes.getDividerType() != DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { 426 // Skip if the split does not have a draggable divider. 427 return; 428 } 429 updateDefaultSplitAttributes(splitContainer, mOverrideSplitType); 430 } 431 updateDefaultSplitAttributes( @onNull SplitContainer splitContainer, @NonNull SplitType overrideSplitType)432 private static void updateDefaultSplitAttributes( 433 @NonNull SplitContainer splitContainer, @NonNull SplitType overrideSplitType) { 434 splitContainer.updateDefaultSplitAttributes( 435 new SplitAttributes.Builder(splitContainer.getDefaultSplitAttributes()) 436 .setSplitType(overrideSplitType) 437 .build() 438 ); 439 } 440 removeSplitContainers(@onNull List<SplitContainer> containers)441 void removeSplitContainers(@NonNull List<SplitContainer> containers) { 442 mSplitContainers.removeAll(containers); 443 } 444 removeSplitPinContainer()445 void removeSplitPinContainer() { 446 if (mSplitPinContainer == null) { 447 return; 448 } 449 450 final TaskFragmentContainer primaryContainer = mSplitPinContainer.getPrimaryContainer(); 451 final TaskFragmentContainer secondaryContainer = mSplitPinContainer.getSecondaryContainer(); 452 mSplitContainers.remove(mSplitPinContainer); 453 mSplitPinContainer = null; 454 455 // Remove the other SplitContainers that contains the unpinned container (unless it 456 // is the current top-most split-pair), since the state are no longer valid. 457 final List<SplitContainer> splitsToRemove = new ArrayList<>(); 458 for (SplitContainer splitContainer : mSplitContainers) { 459 if (splitContainer.getSecondaryContainer().equals(secondaryContainer) 460 && !splitContainer.getPrimaryContainer().equals(primaryContainer)) { 461 splitsToRemove.add(splitContainer); 462 } 463 } 464 removeSplitContainers(splitsToRemove); 465 } 466 467 @Nullable getSplitPinContainer()468 SplitPinContainer getSplitPinContainer() { 469 return mSplitPinContainer; 470 } 471 isTaskFragmentContainerPinned(@onNull TaskFragmentContainer taskFragmentContainer)472 boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) { 473 return mSplitPinContainer != null 474 && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer; 475 } 476 addTaskFragmentContainer(@onNull TaskFragmentContainer taskFragmentContainer)477 void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) { 478 mContainers.add(taskFragmentContainer); 479 onTaskFragmentContainerUpdated(); 480 } 481 addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer)482 void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) { 483 mContainers.add(index, taskFragmentContainer); 484 onTaskFragmentContainerUpdated(); 485 } 486 removeTaskFragmentContainer(@onNull TaskFragmentContainer taskFragmentContainer)487 void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) { 488 mContainers.remove(taskFragmentContainer); 489 onTaskFragmentContainerUpdated(); 490 } 491 removeTaskFragmentContainers(@onNull List<TaskFragmentContainer> taskFragmentContainers)492 void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) { 493 mContainers.removeAll(taskFragmentContainers); 494 onTaskFragmentContainerUpdated(); 495 } 496 clearTaskFragmentContainer()497 void clearTaskFragmentContainer() { 498 mContainers.clear(); 499 onTaskFragmentContainerUpdated(); 500 } 501 502 /** 503 * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on 504 * the returned list. Use {@link #addTaskFragmentContainer}, 505 * {@link #removeTaskFragmentContainer} or other related methods instead. 506 */ 507 @NonNull getTaskFragmentContainers()508 List<TaskFragmentContainer> getTaskFragmentContainers() { 509 return mContainers; 510 } 511 updateTopSplitContainerForDivider( @onNull DividerPresenter dividerPresenter, @NonNull List<TaskFragmentContainer> outContainersToFinish)512 void updateTopSplitContainerForDivider( 513 @NonNull DividerPresenter dividerPresenter, 514 @NonNull List<TaskFragmentContainer> outContainersToFinish) { 515 final SplitContainer topSplitContainer = getTopNonFinishingSplitContainer(); 516 if (topSplitContainer == null) { 517 return; 518 } 519 final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer(); 520 final float newRatio = dividerPresenter.calculateNewSplitRatio(); 521 522 // If the primary container is fully expanded, we should finish all the associated 523 // secondary containers. 524 if (newRatio == RATIO_EXPANDED_PRIMARY) { 525 for (final SplitContainer splitContainer : mSplitContainers) { 526 if (primaryContainer == splitContainer.getPrimaryContainer()) { 527 outContainersToFinish.add(splitContainer.getSecondaryContainer()); 528 } 529 } 530 531 // Temporarily suppress the placeholder rule in the TaskContainer. This will be restored 532 // if a new split is added into the TaskContainer. 533 mPlaceholderRuleSuppressed = true; 534 535 mOverrideSplitType = null; 536 return; 537 } 538 539 final SplitType newSplitType; 540 if (newRatio == RATIO_EXPANDED_SECONDARY) { 541 newSplitType = new ExpandContainersSplitType(); 542 // We do not want to apply ExpandContainersSplitType to new split containers. 543 mOverrideSplitType = null; 544 } else { 545 // We save the override RatioSplitType and apply to new split containers. 546 newSplitType = mOverrideSplitType = new RatioSplitType(newRatio); 547 } 548 for (final SplitContainer splitContainer : mSplitContainers) { 549 if (primaryContainer == splitContainer.getPrimaryContainer()) { 550 updateDefaultSplitAttributes(splitContainer, newSplitType); 551 } 552 } 553 } 554 555 @Nullable getTopNonFinishingSplitContainer()556 SplitContainer getTopNonFinishingSplitContainer() { 557 for (int i = mSplitContainers.size() - 1; i >= 0; i--) { 558 final SplitContainer splitContainer = mSplitContainers.get(i); 559 if (!splitContainer.getPrimaryContainer().isFinished() 560 && !splitContainer.getSecondaryContainer().isFinished()) { 561 return splitContainer; 562 } 563 } 564 return null; 565 } 566 onTaskFragmentContainerUpdated()567 private void onTaskFragmentContainerUpdated() { 568 // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce 569 // another special container that should also be on top in the future. 570 updateSplitPinContainerIfNecessary(); 571 // Update overlay container after split pin container since the overlay should be on top of 572 // pin container. 573 updateAlwaysOnTopOverlayIfNecessary(); 574 } 575 updateAlwaysOnTopOverlayIfNecessary()576 private void updateAlwaysOnTopOverlayIfNecessary() { 577 final List<TaskFragmentContainer> alwaysOnTopOverlays = mContainers 578 .stream().filter(TaskFragmentContainer::isAlwaysOnTopOverlay).toList(); 579 if (alwaysOnTopOverlays.size() > 1) { 580 throw new IllegalStateException("There must be at most one always-on-top overlay " 581 + "container per Task"); 582 } 583 mAlwaysOnTopOverlayContainer = alwaysOnTopOverlays.isEmpty() 584 ? null : alwaysOnTopOverlays.getFirst(); 585 if (mAlwaysOnTopOverlayContainer != null) { 586 moveContainerToLastIfNecessary(mAlwaysOnTopOverlayContainer); 587 } 588 } 589 updateSplitPinContainerIfNecessary()590 private void updateSplitPinContainerIfNecessary() { 591 if (mSplitPinContainer == null) { 592 return; 593 } 594 595 final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer(); 596 final int pinnedContainerIndex = mContainers.indexOf(pinnedContainer); 597 if (pinnedContainerIndex <= 0) { 598 removeSplitPinContainer(); 599 return; 600 } 601 602 // Ensure the pinned container is top-most. 603 moveContainerToLastIfNecessary(pinnedContainer); 604 605 // Update the primary container adjacent to the pinned container if needed. 606 final TaskFragmentContainer adjacentContainer = 607 getNonFinishingTaskFragmentContainerBelow(pinnedContainer); 608 if (adjacentContainer == null) { 609 removeSplitPinContainer(); 610 } else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) { 611 mSplitPinContainer.setPrimaryContainer(adjacentContainer); 612 } 613 } 614 615 /** Moves the {@code container} to the last to align taskFragments' z-order. */ moveContainerToLastIfNecessary(@onNull TaskFragmentContainer container)616 private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) { 617 final int index = mContainers.indexOf(container); 618 if (index < 0) { 619 Log.w(TAG, "The container:" + container + " is not in the container list!"); 620 return; 621 } 622 if (index != mContainers.size() - 1) { 623 mContainers.remove(container); 624 mContainers.add(container); 625 } 626 } 627 628 /** 629 * Gets the descriptors of split states in this Task. 630 * 631 * @return a list of {@code SplitInfo} if all the SplitContainers are stable, or {@code null} if 632 * any SplitContainer is in an intermediate state. 633 */ 634 @Nullable getSplitStatesIfStable()635 List<SplitInfo> getSplitStatesIfStable() { 636 final List<SplitInfo> splitStates = new ArrayList<>(); 637 for (SplitContainer container : mSplitContainers) { 638 final SplitInfo splitInfo = container.toSplitInfoIfStable(); 639 if (splitInfo == null) { 640 return null; 641 } 642 splitStates.add(splitInfo); 643 } 644 return splitStates; 645 } 646 647 // TODO(b/317358445): Makes ActivityStack and SplitInfo callback more stable. 648 /** 649 * Returns a list of currently active {@link ActivityStack activityStacks}. 650 * 651 * @return a list of {@link ActivityStack activityStacks} if all the containers are in 652 * a stable state, or {@code null} otherwise. 653 */ 654 @Nullable getActivityStacksIfStable()655 List<ActivityStack> getActivityStacksIfStable() { 656 final List<ActivityStack> activityStacks = new ArrayList<>(); 657 for (TaskFragmentContainer container : mContainers) { 658 final ActivityStack activityStack = container.toActivityStackIfStable(); 659 if (activityStack == null) { 660 return null; 661 } 662 activityStacks.add(activityStack); 663 } 664 return activityStacks; 665 } 666 667 /** A wrapper class which contains the information of {@link TaskContainer} */ 668 static final class TaskProperties { 669 private final int mDisplayId; 670 @NonNull 671 private final Configuration mConfiguration; 672 TaskProperties(int displayId, @NonNull Configuration configuration)673 TaskProperties(int displayId, @NonNull Configuration configuration) { 674 mDisplayId = displayId; 675 mConfiguration = configuration; 676 } 677 getDisplayId()678 int getDisplayId() { 679 return mDisplayId; 680 } 681 682 @NonNull getConfiguration()683 Configuration getConfiguration() { 684 return mConfiguration; 685 } 686 687 /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */ 688 @NonNull getTaskMetrics()689 WindowMetrics getTaskMetrics() { 690 final Rect taskBounds = mConfiguration.windowConfiguration.getBounds(); 691 // TODO(b/190433398): Supply correct insets. 692 final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; 693 return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density); 694 } 695 696 /** Translates the given absolute bounds to relative bounds in this Task coordinate. */ translateAbsoluteBoundsToRelativeBounds(@onNull Rect inOutBounds)697 void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) { 698 if (inOutBounds.isEmpty()) { 699 return; 700 } 701 final Rect taskBounds = mConfiguration.windowConfiguration.getBounds(); 702 inOutBounds.offset(-taskBounds.left, -taskBounds.top); 703 } 704 705 /** 706 * Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is 707 * associated with. 708 * <p> 709 * Note that for most case, caller should use 710 * {@link SplitPresenter#getTaskProperties(Activity)} instead. This method is used before 711 * the {@code activity} goes into split. 712 * </p><p> 713 * If the {@link Activity} is in fullscreen, override 714 * {@link WindowConfiguration#getBounds()} with {@link WindowConfiguration#getMaxBounds()} 715 * in case the {@link Activity} is letterboxed. Otherwise, get the Task 716 * {@link Configuration} from the server side or use {@link Activity}'s 717 * {@link Configuration} as a fallback if the Task {@link Configuration} cannot be obtained. 718 */ 719 @NonNull getTaskPropertiesFromActivity(@onNull Activity activity)720 static TaskProperties getTaskPropertiesFromActivity(@NonNull Activity activity) { 721 final int displayId = activity.getDisplayId(); 722 // Use a copy of configuration because activity's configuration may be updated later, 723 // or we may get unexpected TaskContainer's configuration if Activity's configuration is 724 // updated. An example is Activity is going to be in split. 725 final Configuration activityConfig = new Configuration( 726 activity.getResources().getConfiguration()); 727 final WindowConfiguration windowConfiguration = activityConfig.windowConfiguration; 728 final int windowingMode = windowConfiguration.getWindowingMode(); 729 if (!inMultiWindowMode(windowingMode)) { 730 // Use the max bounds in fullscreen in case the Activity is letterboxed. 731 windowConfiguration.setBounds(windowConfiguration.getMaxBounds()); 732 return new TaskProperties(displayId, activityConfig); 733 } 734 final Configuration taskConfig = ActivityClient.getInstance() 735 .getTaskConfiguration(activity.getActivityToken()); 736 if (taskConfig == null) { 737 Log.w(TAG, "Could not obtain task configuration for activity:" + activity); 738 // Still report activity config if task config cannot be obtained from the server 739 // side. 740 return new TaskProperties(displayId, activityConfig); 741 } 742 return new TaskProperties(displayId, taskConfig); 743 } 744 } 745 } 746