1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.window.extensions.embedding; 18 19 import static android.app.ActivityManager.START_SUCCESS; 20 import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 23 import static android.view.Display.DEFAULT_DISPLAY; 24 import static android.view.WindowManager.TRANSIT_CLOSE; 25 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; 26 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; 27 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; 28 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; 29 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; 30 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE; 31 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE; 32 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; 33 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; 34 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; 35 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; 36 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; 37 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; 38 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; 39 40 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_ACTIVITY_STACK_TOKEN; 41 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG; 42 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; 43 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; 44 import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; 45 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; 46 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; 47 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; 48 import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair; 49 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; 50 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; 51 import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; 52 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; 53 import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams; 54 55 import android.annotation.CallbackExecutor; 56 import android.app.Activity; 57 import android.app.ActivityClient; 58 import android.app.ActivityOptions; 59 import android.app.ActivityThread; 60 import android.app.Application; 61 import android.app.Instrumentation; 62 import android.app.servertransaction.ClientTransactionListenerController; 63 import android.content.ComponentName; 64 import android.content.Context; 65 import android.content.Intent; 66 import android.content.res.Configuration; 67 import android.graphics.Rect; 68 import android.os.Bundle; 69 import android.os.Handler; 70 import android.os.IBinder; 71 import android.os.Looper; 72 import android.util.ArrayMap; 73 import android.util.ArraySet; 74 import android.util.Log; 75 import android.util.Pair; 76 import android.util.Size; 77 import android.util.SparseArray; 78 import android.view.WindowMetrics; 79 import android.window.ActivityWindowInfo; 80 import android.window.TaskFragmentAnimationParams; 81 import android.window.TaskFragmentInfo; 82 import android.window.TaskFragmentOperation; 83 import android.window.TaskFragmentParentInfo; 84 import android.window.TaskFragmentTransaction; 85 import android.window.WindowContainerTransaction; 86 87 import androidx.annotation.GuardedBy; 88 import androidx.annotation.NonNull; 89 import androidx.annotation.Nullable; 90 import androidx.window.common.CommonFoldingFeature; 91 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; 92 import androidx.window.common.EmptyLifecycleCallbacksAdapter; 93 import androidx.window.extensions.WindowExtensions; 94 import androidx.window.extensions.core.util.function.Consumer; 95 import androidx.window.extensions.core.util.function.Function; 96 import androidx.window.extensions.core.util.function.Predicate; 97 import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; 98 import androidx.window.extensions.layout.WindowLayoutComponentImpl; 99 100 import com.android.internal.annotations.VisibleForTesting; 101 import com.android.window.flags.Flags; 102 103 import java.util.ArrayList; 104 import java.util.Collections; 105 import java.util.List; 106 import java.util.Objects; 107 import java.util.Set; 108 import java.util.concurrent.Executor; 109 import java.util.function.BiConsumer; 110 111 /** 112 * Main controller class that manages split states and presentation. 113 */ 114 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, 115 ActivityEmbeddingComponent, DividerPresenter.DragEventCallback { 116 static final String TAG = "SplitController"; 117 static final boolean ENABLE_SHELL_TRANSITIONS = true; 118 119 // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without 120 // association. It's not set in WM Extensions nor Wm Jetpack library currently. 121 private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY = 122 "androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity"; 123 124 @VisibleForTesting 125 @GuardedBy("mLock") 126 final SplitPresenter mPresenter; 127 128 @VisibleForTesting 129 @GuardedBy("mLock") 130 final TransactionManager mTransactionManager; 131 132 // Currently applied split configuration. 133 @GuardedBy("mLock") 134 private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); 135 136 /** 137 * Stores the token of the associated Activity that maps to the 138 * {@link OverlayContainerRestoreParams} of the most recent created overlay container. 139 */ 140 @GuardedBy("mLock") 141 final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>(); 142 143 /** 144 * A developer-defined {@link SplitAttributes} calculator to compute the current 145 * {@link SplitAttributes} with the current device and window states. 146 * It is registered via {@link #setSplitAttributesCalculator(Function)} 147 * and unregistered via {@link #clearSplitAttributesCalculator()}. 148 * This is called when: 149 * <ul> 150 * <li>{@link SplitPresenter#updateSplitContainer}</li> 151 * <li>There's a started Activity which matches {@link SplitPairRule} </li> 152 * <li>Checking whether the place holder should be launched if there's a Activity matches 153 * {@link SplitPlaceholderRule} </li> 154 * </ul> 155 */ 156 @GuardedBy("mLock") 157 @Nullable 158 private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator; 159 160 /** 161 * A calculator function to compute {@link ActivityStack} attributes in a task, which is called 162 * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed. 163 */ 164 @GuardedBy("mLock") 165 @Nullable 166 private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> 167 mActivityStackAttributesCalculator; 168 169 /** 170 * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info 171 * below it. 172 * When the app is host of multiple Tasks, there can be multiple splits controlled by the same 173 * organizer. 174 */ 175 @VisibleForTesting 176 @GuardedBy("mLock") 177 final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); 178 179 /** Map from Task id to {@link DividerPresenter} which manages the divider in the Task. */ 180 @GuardedBy("mLock") 181 private final SparseArray<DividerPresenter> mDividerPresenters = new SparseArray<>(); 182 183 /** Callback to Jetpack to notify about changes to split states. */ 184 @GuardedBy("mLock") 185 @Nullable 186 private Consumer<List<SplitInfo>> mSplitInfoCallback; 187 private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); 188 189 /** 190 * Stores callbacks to Jetpack to notify about changes to {@link ActivityStack activityStacks} 191 * and corresponding {@link Executor executors} to dispatch the callback. 192 */ 193 @GuardedBy("mLock") 194 @NonNull 195 private final ArrayMap<Consumer<List<ActivityStack>>, Executor> mActivityStackCallbacks = 196 new ArrayMap<>(); 197 198 private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>(); 199 200 /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */ 201 @GuardedBy("mLock") 202 @Nullable 203 private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>> 204 mEmbeddedActivityWindowInfoCallback; 205 206 /** Listener registered to {@link ClientTransactionListenerController}. */ 207 @GuardedBy("mLock") 208 @Nullable 209 private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener = 210 Flags.activityWindowInfoFlag() 211 ? this::onActivityWindowInfoChanged 212 : null; 213 214 private final Handler mHandler; 215 private final MainThreadExecutor mExecutor; 216 final Object mLock = new Object(); 217 private final ActivityStartMonitor mActivityStartMonitor; 218 SplitController(@onNull WindowLayoutComponentImpl windowLayoutComponent, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer)219 public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent, 220 @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { 221 Log.i(TAG, "Initializing Activity Embedding Controller."); 222 mExecutor = new MainThreadExecutor(); 223 mHandler = mExecutor.mHandler; 224 mPresenter = new SplitPresenter(mExecutor, windowLayoutComponent, this); 225 mTransactionManager = new TransactionManager(mPresenter); 226 final ActivityThread activityThread = ActivityThread.currentActivityThread(); 227 final Application application = activityThread.getApplication(); 228 // Register a callback to be notified about activities being created. 229 application.registerActivityLifecycleCallbacks(new LifecycleCallbacks()); 230 // Intercept activity starts to route activities to new containers if necessary. 231 Instrumentation instrumentation = activityThread.getInstrumentation(); 232 233 mActivityStartMonitor = new ActivityStartMonitor(); 234 instrumentation.addMonitor(mActivityStartMonitor); 235 foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener()); 236 } 237 238 private class FoldingFeatureListener 239 implements java.util.function.Consumer<List<CommonFoldingFeature>> { 240 @Override accept(List<CommonFoldingFeature> foldingFeatures)241 public void accept(List<CommonFoldingFeature> foldingFeatures) { 242 synchronized (mLock) { 243 final TransactionRecord transactionRecord = mTransactionManager 244 .startNewTransaction(); 245 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 246 for (int i = 0; i < mTaskContainers.size(); i++) { 247 final TaskContainer taskContainer = mTaskContainers.valueAt(i); 248 if (!taskContainer.isVisible()) { 249 continue; 250 } 251 if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) { 252 continue; 253 } 254 // TODO(b/238948678): Support reporting display features in all windowing modes. 255 if (taskContainer.isInMultiWindow()) { 256 continue; 257 } 258 if (taskContainer.isEmpty()) { 259 continue; 260 } 261 updateContainersInTask(wct, taskContainer); 262 } 263 // The WCT should be applied and merged to the device state change transition if 264 // there is one. 265 transactionRecord.apply(false /* shouldApplyIndependently */); 266 } 267 } 268 } 269 270 /** Updates the embedding rules applied to future activity launches. */ 271 @Override setEmbeddingRules(@onNull Set<EmbeddingRule> rules)272 public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) { 273 synchronized (mLock) { 274 Log.i(TAG, "Setting embedding rules. Size: " + rules.size()); 275 mSplitRules.clear(); 276 mSplitRules.addAll(rules); 277 } 278 } 279 280 @Override pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule)281 public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) { 282 synchronized (mLock) { 283 Log.i(TAG, "Request to pin top activity stack."); 284 final TaskContainer task = getTaskContainer(taskId); 285 if (task == null) { 286 Log.e(TAG, "Cannot find the task for id: " + taskId); 287 return false; 288 } 289 290 final TaskFragmentContainer topContainer = 291 task.getTopNonFinishingTaskFragmentContainer(); 292 // Cannot pin the TaskFragment if no other TaskFragment behind it. 293 if (topContainer == null || task.indexOf(topContainer) <= 0) { 294 Log.w(TAG, "Cannot find an ActivityStack to pin or split"); 295 return false; 296 } 297 // Abort if the top container is already pinned. 298 if (task.getSplitPinContainer() != null) { 299 Log.w(TAG, "There is already a pinned ActivityStack."); 300 return false; 301 } 302 303 // Find a valid adjacent TaskFragmentContainer 304 final TaskFragmentContainer primaryContainer = 305 task.getNonFinishingTaskFragmentContainerBelow(topContainer); 306 if (primaryContainer == null) { 307 Log.w(TAG, "Cannot find another ActivityStack to split"); 308 return false; 309 } 310 311 // Abort if no space to split. 312 final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( 313 task.getTaskProperties(), splitPinRule, 314 splitPinRule.getDefaultSplitAttributes(), 315 getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(), 316 topContainer.getTopNonFinishingActivity())); 317 if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) { 318 Log.w(TAG, "No space to split, abort pinning top ActivityStack."); 319 return false; 320 } 321 322 // Registers a Split 323 final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer, 324 topContainer, splitPinRule, calculatedSplitAttributes); 325 task.addSplitContainer(splitPinContainer); 326 327 // Updates the Split 328 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 329 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 330 mPresenter.updateSplitContainer(splitPinContainer, wct); 331 transactionRecord.apply(false /* shouldApplyIndependently */); 332 updateCallbackIfNecessary(); 333 return true; 334 } 335 } 336 337 @Override unpinTopActivityStack(int taskId)338 public void unpinTopActivityStack(int taskId) { 339 synchronized (mLock) { 340 Log.i(TAG, "Request to unpin top activity stack."); 341 final TaskContainer task = getTaskContainer(taskId); 342 if (task == null) { 343 Log.e(TAG, "Cannot find the task to unpin, id: " + taskId); 344 return; 345 } 346 347 final SplitPinContainer splitPinContainer = task.getSplitPinContainer(); 348 if (splitPinContainer == null) { 349 Log.e(TAG, "No ActivityStack is pinned."); 350 return; 351 } 352 353 // Remove the SplitPinContainer from the task. 354 final TaskFragmentContainer containerToUnpin = 355 splitPinContainer.getSecondaryContainer(); 356 task.removeSplitPinContainer(); 357 358 // Resets the isolated navigation and updates the container. 359 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 360 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 361 mPresenter.setTaskFragmentPinned(wct, containerToUnpin, false /* pinned */); 362 updateContainer(wct, containerToUnpin); 363 transactionRecord.apply(false /* shouldApplyIndependently */); 364 updateCallbackIfNecessary(); 365 } 366 } 367 368 @Override setSplitAttributesCalculator( @onNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator)369 public void setSplitAttributesCalculator( 370 @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) { 371 synchronized (mLock) { 372 mSplitAttributesCalculator = calculator; 373 } 374 } 375 376 @Override clearSplitAttributesCalculator()377 public void clearSplitAttributesCalculator() { 378 synchronized (mLock) { 379 mSplitAttributesCalculator = null; 380 } 381 } 382 383 @Override setActivityStackAttributesCalculator( @onNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> calculator)384 public void setActivityStackAttributesCalculator( 385 @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> 386 calculator) { 387 if (!Flags.activityEmbeddingOverlayPresentationFlag()) { 388 return; 389 } 390 synchronized (mLock) { 391 mActivityStackAttributesCalculator = calculator; 392 } 393 } 394 395 @Override clearActivityStackAttributesCalculator()396 public void clearActivityStackAttributesCalculator() { 397 if (!Flags.activityEmbeddingOverlayPresentationFlag()) { 398 return; 399 } 400 synchronized (mLock) { 401 mActivityStackAttributesCalculator = null; 402 } 403 } 404 405 @GuardedBy("mLock") 406 @Nullable getSplitAttributesCalculator()407 Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() { 408 return mSplitAttributesCalculator; 409 } 410 411 // TODO(b/295993745): remove after we migrate to the bundle approach. 412 @NonNull setLaunchingActivityStack(@onNull ActivityOptions options, @NonNull IBinder token)413 public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options, 414 @NonNull IBinder token) { 415 options.setLaunchTaskFragmentToken(token); 416 return options; 417 } 418 419 @NonNull 420 @GuardedBy("mLock") 421 @VisibleForTesting getSplitRules()422 List<EmbeddingRule> getSplitRules() { 423 return mSplitRules; 424 } 425 426 /** 427 * Registers the split organizer callback to notify about changes to active splits. 428 * 429 * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with 430 * {@link WindowExtensions#getVendorApiLevel()} 2. 431 */ 432 @Deprecated 433 @Override setSplitInfoCallback( @onNull java.util.function.Consumer<List<SplitInfo>> callback)434 public void setSplitInfoCallback( 435 @NonNull java.util.function.Consumer<List<SplitInfo>> callback) { 436 Consumer<List<SplitInfo>> oemConsumer = callback::accept; 437 setSplitInfoCallback(oemConsumer); 438 } 439 440 /** 441 * Registers the split organizer callback to notify about changes to active splits. 442 * 443 * @since {@link WindowExtensions#getVendorApiLevel()} 2 444 */ 445 @Override setSplitInfoCallback(Consumer<List<SplitInfo>> callback)446 public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) { 447 synchronized (mLock) { 448 mSplitInfoCallback = callback; 449 updateSplitInfoCallbackIfNecessary(); 450 } 451 } 452 453 /** 454 * Clears the listener set in {@link SplitController#setSplitInfoCallback(Consumer)}. 455 */ 456 @Override clearSplitInfoCallback()457 public void clearSplitInfoCallback() { 458 synchronized (mLock) { 459 mSplitInfoCallback = null; 460 } 461 } 462 463 /** 464 * Registers the callback for the {@link ActivityStack} state change. 465 * 466 * @param executor The executor to dispatch the callback. 467 * @param callback The callback for this {@link ActivityStack} state change. 468 */ 469 @Override registerActivityStackCallback(@onNull @allbackExecutor Executor executor, @NonNull Consumer<List<ActivityStack>> callback)470 public void registerActivityStackCallback(@NonNull @CallbackExecutor Executor executor, 471 @NonNull Consumer<List<ActivityStack>> callback) { 472 Objects.requireNonNull(executor); 473 Objects.requireNonNull(callback); 474 475 synchronized (mLock) { 476 mActivityStackCallbacks.put(callback, executor); 477 updateActivityStackCallbackIfNecessary(); 478 } 479 } 480 481 /** @see #registerActivityStackCallback(Executor, Consumer) */ 482 @Override unregisterActivityStackCallback(@onNull Consumer<List<ActivityStack>> callback)483 public void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) { 484 Objects.requireNonNull(callback); 485 486 synchronized (mLock) { 487 mActivityStackCallbacks.remove(callback); 488 } 489 } 490 491 @Override finishActivityStacks(@onNull Set<IBinder> activityStackTokens)492 public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) { 493 if (activityStackTokens.isEmpty()) { 494 return; 495 } 496 synchronized (mLock) { 497 // Translate ActivityStack to TaskFragmentContainer. 498 final List<TaskFragmentContainer> pendingFinishingContainers = 499 activityStackTokens.stream().map(token -> { 500 synchronized (mLock) { 501 return getContainer(token); 502 } 503 }).filter(Objects::nonNull).toList(); 504 505 if (pendingFinishingContainers.isEmpty()) { 506 return; 507 } 508 // Start transaction with close transit type. 509 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 510 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 511 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 512 513 forAllTaskContainers(taskContainer -> { 514 synchronized (mLock) { 515 final List<TaskFragmentContainer> containers = 516 taskContainer.getTaskFragmentContainers(); 517 // Clean up the TaskFragmentContainers by the z-order from the lowest. 518 for (int i = 0; i < containers.size(); i++) { 519 final TaskFragmentContainer container = containers.get(i); 520 if (pendingFinishingContainers.contains(container)) { 521 // Don't update records here to prevent double invocation. 522 container.finish(false /* shouldFinishDependant */, mPresenter, 523 wct, this, false /* shouldRemoveRecord */); 524 } 525 } 526 // Remove container records. 527 removeContainers(taskContainer, pendingFinishingContainers); 528 // Update the change to the server side. 529 updateContainersInTaskIfVisible(wct, taskContainer.getTaskId()); 530 } 531 }); 532 533 // Apply the transaction. 534 transactionRecord.apply(false /* shouldApplyIndependently */); 535 } 536 } 537 538 @Override invalidateTopVisibleSplitAttributes()539 public void invalidateTopVisibleSplitAttributes() { 540 synchronized (mLock) { 541 WindowContainerTransaction wct = mTransactionManager.startNewTransaction() 542 .getTransaction(); 543 forAllTaskContainers(taskContainer -> { 544 synchronized (mLock) { 545 updateContainersInTaskIfVisible(wct, taskContainer.getTaskId()); 546 } 547 }); 548 mTransactionManager.getCurrentTransactionRecord() 549 .apply(false /* shouldApplyIndependently */); 550 } 551 } 552 553 @GuardedBy("mLock") forAllTaskContainers(@onNull Consumer<TaskContainer> callback)554 private void forAllTaskContainers(@NonNull Consumer<TaskContainer> callback) { 555 for (int i = mTaskContainers.size() - 1; i >= 0; --i) { 556 callback.accept(mTaskContainers.valueAt(i)); 557 } 558 } 559 560 @Override updateSplitAttributes(@onNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes)561 public void updateSplitAttributes(@NonNull IBinder splitInfoToken, 562 @NonNull SplitAttributes splitAttributes) { 563 Objects.requireNonNull(splitInfoToken); 564 Objects.requireNonNull(splitAttributes); 565 synchronized (mLock) { 566 final SplitContainer splitContainer = getSplitContainer(splitInfoToken); 567 if (splitContainer == null) { 568 Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken); 569 return; 570 } 571 // Override the default split Attributes so that it will be applied 572 // if the SplitContainer is not visible currently. 573 splitContainer.updateDefaultSplitAttributes(splitAttributes); 574 575 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 576 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 577 if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) { 578 transactionRecord.apply(false /* shouldApplyIndependently */); 579 } else { 580 // Abort if the SplitContainer wasn't updated. 581 transactionRecord.abort(); 582 } 583 } 584 } 585 586 @Override updateActivityStackAttributes(@onNull ActivityStack.Token activityStackToken, @NonNull ActivityStackAttributes attributes)587 public void updateActivityStackAttributes(@NonNull ActivityStack.Token activityStackToken, 588 @NonNull ActivityStackAttributes attributes) { 589 if (!Flags.activityEmbeddingOverlayPresentationFlag()) { 590 return; 591 } 592 Objects.requireNonNull(activityStackToken); 593 Objects.requireNonNull(attributes); 594 595 synchronized (mLock) { 596 final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken()); 597 if (container == null) { 598 Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken); 599 return; 600 } 601 if (!container.isOverlay()) { 602 Log.w(TAG, "Updating non-overlay container has not supported yet!"); 603 return; 604 } 605 606 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 607 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 608 mPresenter.applyActivityStackAttributes(wct, container, attributes, 609 container.getMinDimensions()); 610 transactionRecord.apply(false /* shouldApplyIndependently */); 611 } 612 } 613 614 @Override 615 @Nullable getParentContainerInfo( @onNull ActivityStack.Token activityStackToken)616 public ParentContainerInfo getParentContainerInfo( 617 @NonNull ActivityStack.Token activityStackToken) { 618 if (!Flags.activityEmbeddingOverlayPresentationFlag()) { 619 return null; 620 } 621 Objects.requireNonNull(activityStackToken); 622 synchronized (mLock) { 623 final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken()); 624 if (container == null) { 625 return null; 626 } 627 final TaskContainer.TaskProperties properties = container.getTaskContainer() 628 .getTaskProperties(); 629 return mPresenter.createParentContainerInfoFromTaskProperties(properties); 630 } 631 } 632 633 @Override 634 @Nullable getActivityStackToken(@onNull String tag)635 public ActivityStack.Token getActivityStackToken(@NonNull String tag) { 636 if (!Flags.activityEmbeddingOverlayPresentationFlag()) { 637 return null; 638 } 639 Objects.requireNonNull(tag); 640 synchronized (mLock) { 641 final TaskFragmentContainer taskFragmentContainer = 642 getContainer(container -> tag.equals(container.getOverlayTag())); 643 if (taskFragmentContainer == null) { 644 return null; 645 } 646 return ActivityStack.Token.createFromBinder(taskFragmentContainer 647 .getTaskFragmentToken()); 648 } 649 } 650 651 /** 652 * Called when the transaction is ready so that the organizer can update the TaskFragments based 653 * on the changes in transaction. 654 */ 655 @Override onTransactionReady(@onNull TaskFragmentTransaction transaction)656 public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { 657 synchronized (mLock) { 658 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction( 659 transaction.getTransactionToken()); 660 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 661 final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); 662 for (TaskFragmentTransaction.Change change : changes) { 663 final int taskId = change.getTaskId(); 664 final TaskFragmentInfo info = change.getTaskFragmentInfo(); 665 switch (change.getType()) { 666 case TYPE_TASK_FRAGMENT_APPEARED: 667 mPresenter.updateTaskFragmentInfo(info); 668 onTaskFragmentAppeared(wct, info); 669 break; 670 case TYPE_TASK_FRAGMENT_INFO_CHANGED: 671 mPresenter.updateTaskFragmentInfo(info); 672 onTaskFragmentInfoChanged(wct, info); 673 break; 674 case TYPE_TASK_FRAGMENT_VANISHED: 675 mPresenter.removeTaskFragmentInfo(info); 676 onTaskFragmentVanished(wct, info); 677 break; 678 case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: 679 onTaskFragmentParentInfoChanged(wct, taskId, 680 change.getTaskFragmentParentInfo()); 681 break; 682 case TYPE_TASK_FRAGMENT_ERROR: 683 final Bundle errorBundle = change.getErrorBundle(); 684 final IBinder errorToken = change.getErrorCallbackToken(); 685 final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable( 686 KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class); 687 final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE); 688 final Throwable exception = errorBundle.getSerializable( 689 KEY_ERROR_CALLBACK_THROWABLE, Throwable.class); 690 if (errorTaskFragmentInfo != null) { 691 mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo); 692 } 693 onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType, 694 exception); 695 break; 696 case TYPE_ACTIVITY_REPARENTED_TO_TASK: 697 final IBinder candidateAssociatedActToken, lastOverlayToken; 698 if (Flags.fixPipRestoreToOverlay()) { 699 candidateAssociatedActToken = change.getOtherActivityToken(); 700 lastOverlayToken = change.getTaskFragmentToken(); 701 } else { 702 candidateAssociatedActToken = lastOverlayToken = null; 703 } 704 onActivityReparentedToTask( 705 wct, 706 taskId, 707 change.getActivityIntent(), 708 change.getActivityToken(), 709 candidateAssociatedActToken, 710 lastOverlayToken); 711 break; 712 default: 713 throw new IllegalArgumentException( 714 "Unknown TaskFragmentEvent=" + change.getType()); 715 } 716 } 717 718 // Notify the server, and the server should apply and merge the 719 // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction. 720 transactionRecord.apply(false /* shouldApplyIndependently */); 721 updateCallbackIfNecessary(); 722 } 723 } 724 725 /** 726 * Called when a TaskFragment is created and organized by this organizer. 727 * 728 * @param wct The {@link WindowContainerTransaction} to make any changes with if 729 * needed. 730 * @param taskFragmentInfo Info of the TaskFragment that is created. 731 */ 732 // Suppress GuardedBy warning because lint ask to mark this method as 733 // @GuardedBy(container.mController.mLock), which is mLock itself 734 @SuppressWarnings("GuardedBy") 735 @VisibleForTesting 736 @GuardedBy("mLock") onTaskFragmentAppeared(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)737 void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, 738 @NonNull TaskFragmentInfo taskFragmentInfo) { 739 final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); 740 if (container == null) { 741 return; 742 } 743 744 container.setInfo(wct, taskFragmentInfo); 745 if (container.isFinished()) { 746 mTransactionManager.getCurrentTransactionRecord() 747 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 748 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 749 } else { 750 // Update with the latest Task configuration. 751 updateContainer(wct, container); 752 } 753 } 754 755 /** 756 * Called when the status of an organized TaskFragment is changed. 757 * 758 * @param wct The {@link WindowContainerTransaction} to make any changes with if 759 * needed. 760 * @param taskFragmentInfo Info of the TaskFragment that is changed. 761 */ 762 // Suppress GuardedBy warning because lint ask to mark this method as 763 // @GuardedBy(container.mController.mLock), which is mLock itself 764 @SuppressWarnings("GuardedBy") 765 @VisibleForTesting 766 @GuardedBy("mLock") onTaskFragmentInfoChanged(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)767 void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, 768 @NonNull TaskFragmentInfo taskFragmentInfo) { 769 final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); 770 if (container == null) { 771 return; 772 } 773 774 final boolean wasInPip = isInPictureInPicture(container); 775 container.setInfo(wct, taskFragmentInfo); 776 final boolean isInPip = isInPictureInPicture(container); 777 // Check if there are no running activities - consider the container empty if there are 778 // no non-finishing activities left. 779 if (!taskFragmentInfo.hasRunningActivity()) { 780 if (taskFragmentInfo.isTaskFragmentClearedForPip()) { 781 // Do not finish the dependents if the last activity is reparented to PiP. 782 // Instead, the original split should be cleanup, and the dependent may be 783 // expanded to fullscreen. 784 mTransactionManager.getCurrentTransactionRecord() 785 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 786 cleanupForEnterPip(wct, container); 787 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 788 } else if (taskFragmentInfo.isTaskClearedForReuse()) { 789 // Do not finish the dependents if this TaskFragment was cleared due to 790 // launching activity in the Task. 791 mTransactionManager.getCurrentTransactionRecord() 792 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 793 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 794 } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) { 795 // Do not finish the dependents if this TaskFragment was cleared to reorder 796 // the launching Activity to front of the Task. 797 mTransactionManager.getCurrentTransactionRecord() 798 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 799 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 800 } else if (!container.isWaitingActivityAppear()) { 801 // Do not finish the container before the expected activity appear until 802 // timeout. 803 mTransactionManager.getCurrentTransactionRecord() 804 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 805 mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); 806 } 807 } else if (wasInPip && isInPip) { 808 // No update until exit PIP. 809 return; 810 } else if (isInPip) { 811 // Enter PIP. 812 // All overrides will be cleanup. 813 container.setLastRequestedBounds(null /* bounds */); 814 container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED); 815 container.clearLastAdjacentTaskFragment(); 816 container.setLastCompanionTaskFragment(null /* fragmentToken */); 817 container.setLastRequestAnimationParams(TaskFragmentAnimationParams.DEFAULT); 818 cleanupForEnterPip(wct, container); 819 } else if (wasInPip) { 820 // Exit PIP. 821 // Updates the presentation of the container. Expand or launch placeholder if 822 // needed. 823 updateContainer(wct, container); 824 } 825 } 826 827 /** 828 * Called when an organized TaskFragment is removed. 829 * 830 * @param wct The {@link WindowContainerTransaction} to make any changes with if 831 * needed. 832 * @param taskFragmentInfo Info of the TaskFragment that is removed. 833 */ 834 @VisibleForTesting 835 @GuardedBy("mLock") onTaskFragmentVanished(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)836 void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, 837 @NonNull TaskFragmentInfo taskFragmentInfo) { 838 final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); 839 if (container != null) { 840 // Cleanup if the TaskFragment vanished is not requested by the organizer. 841 removeContainer(container); 842 // Make sure the containers in the Task are up-to-date. 843 updateContainersInTaskIfVisible(wct, container.getTaskId()); 844 } 845 cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); 846 } 847 848 /** 849 * Called when the parent leaf Task of organized TaskFragments is changed. 850 * When the leaf Task is changed, the organizer may want to update the TaskFragments in one 851 * transaction. 852 * <p> 853 * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged} 854 * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there 855 * can be an override bounds. 856 * 857 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 858 * @param taskId Id of the parent Task that is changed. 859 * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task. 860 */ 861 @VisibleForTesting 862 @GuardedBy("mLock") onTaskFragmentParentInfoChanged(@onNull WindowContainerTransaction wct, int taskId, @NonNull TaskFragmentParentInfo parentInfo)863 void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, 864 int taskId, @NonNull TaskFragmentParentInfo parentInfo) { 865 final TaskContainer taskContainer = getTaskContainer(taskId); 866 if (taskContainer == null || taskContainer.isEmpty()) { 867 Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId); 868 return; 869 } 870 871 if (!parentInfo.isVisible()) { 872 // Only making the TaskContainer invisible and drops the other info, and perform the 873 // update when the next time the Task becomes visible. 874 if (taskContainer.isVisible()) { 875 taskContainer.setInvisible(); 876 } 877 return; 878 } 879 880 // Checks if container should be updated before apply new parentInfo. 881 final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo); 882 taskContainer.updateTaskFragmentParentInfo(parentInfo); 883 884 // The divider need to be updated even if shouldUpdateContainer is false, because the decor 885 // surface may change in TaskFragmentParentInfo, which requires divider update but not 886 // container update. 887 updateDivider(wct, taskContainer); 888 889 // If the last direct activity of the host task is dismissed and there's an always-on-top 890 // overlay container in the task, the overlay container should also be dismissed. 891 dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer); 892 893 if (!shouldUpdateContainer) { 894 return; 895 } 896 updateContainersInTask(wct, taskContainer); 897 } 898 899 @GuardedBy("mLock") updateContainersInTaskIfVisible(@onNull WindowContainerTransaction wct, int taskId)900 void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { 901 final TaskContainer taskContainer = getTaskContainer(taskId); 902 if (taskContainer == null) { 903 return; 904 } 905 906 if (taskContainer.isVisible()) { 907 updateContainersInTask(wct, taskContainer); 908 } else if (Flags.fixNoContainerUpdateWithoutResize()) { 909 // the TaskFragmentContainers need to be updated when the task becomes visible 910 taskContainer.mTaskFragmentContainersNeedsUpdate = true; 911 } 912 } 913 914 @GuardedBy("mLock") updateContainersInTask(@onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)915 private void updateContainersInTask(@NonNull WindowContainerTransaction wct, 916 @NonNull TaskContainer taskContainer) { 917 taskContainer.mTaskFragmentContainersNeedsUpdate = false; 918 919 // Update all TaskFragments in the Task. Make a copy of the list since some may be 920 // removed on updating. 921 final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); 922 for (int i = containers.size() - 1; i >= 0; i--) { 923 final TaskFragmentContainer container = containers.get(i); 924 // Wait until onTaskFragmentAppeared to update new container. 925 if (!container.isFinished() && !container.isWaitingActivityAppear()) { 926 updateContainer(wct, container); 927 } 928 } 929 } 930 931 /** 932 * Called when an Activity is reparented to the Task with organized TaskFragment. For example, 933 * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its 934 * original Task. In this case, we need to notify the organizer so that it can check if the 935 * Activity matches any split rule. 936 * 937 * @param wct The {@link WindowContainerTransaction} to make any changes with if 938 * needed. 939 * @param taskId The Task that the activity is reparented to. 940 * @param activityIntent The intent that the activity is original launched with. 941 * @param activityToken If the activity belongs to the same process as the organizer, this 942 * will be the actual activity token; if the activity belongs to a 943 * different process, the server will generate a temporary token that 944 * the organizer can use to reparent the activity through 945 * {@link WindowContainerTransaction} if needed. 946 * @param candidateAssociatedActToken The token of the candidate associated-activity. 947 * @param lastOverlayToken The last parent overlay container token. 948 */ 949 @VisibleForTesting 950 @GuardedBy("mLock") onActivityReparentedToTask(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken, @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken)951 void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, 952 int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken, 953 @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) { 954 // Reparent the activity to an overlay container if needed. 955 final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams( 956 candidateAssociatedActToken, lastOverlayToken); 957 if (params != null) { 958 final Activity associatedActivity = getActivity(candidateAssociatedActToken); 959 final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded( 960 wct, params.mOptions, params.mIntent, associatedActivity); 961 if (targetContainer != null) { 962 wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), 963 activityToken); 964 return; 965 } 966 } 967 968 // If the activity belongs to the current app process, we treat it as a new activity 969 // launch. 970 final Activity activity = getActivity(activityToken); 971 if (activity != null) { 972 // We don't allow split as primary for new launch because we currently only support 973 // launching to top. We allow split as primary for activity reparent because the 974 // activity may be split as primary before it is reparented out. In that case, we 975 // want to show it as primary again when it is reparented back. 976 if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) { 977 // When there is no embedding rule matched, try to place it in the top container 978 // like a normal launch. 979 placeActivityInTopContainer(wct, activity); 980 } 981 return; 982 } 983 984 final TaskContainer taskContainer = getTaskContainer(taskId); 985 if (taskContainer == null || taskContainer.isInPictureInPicture()) { 986 // We don't embed activity when it is in PIP. 987 return; 988 } 989 990 // If the activity belongs to a different app process, we treat it as starting new 991 // intent, since both actions might result in a new activity that should appear in an 992 // organized TaskFragment. 993 TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId, 994 activityIntent, null /* launchingActivity */); 995 if (targetContainer == null) { 996 // When there is no embedding rule matched, try to place it in the top container 997 // like a normal launch. 998 // TODO(b/301034784): Check if it makes sense to place the activity in overlay 999 // container. 1000 targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer(); 1001 } 1002 if (targetContainer == null) { 1003 return; 1004 } 1005 wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), 1006 activityToken); 1007 // Because the activity does not belong to the organizer process, we wait until 1008 // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). 1009 } 1010 1011 /** 1012 * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code 1013 * associatedActivityToken} associated with and only if data matches the {@code overlayToken}. 1014 * Otherwise, return {@code null}. 1015 */ 1016 @VisibleForTesting 1017 @GuardedBy("mLock") 1018 @Nullable getOverlayContainerRestoreParams( @ullable IBinder associatedActivityToken, @Nullable IBinder overlayToken)1019 OverlayContainerRestoreParams getOverlayContainerRestoreParams( 1020 @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) { 1021 if (!Flags.fixPipRestoreToOverlay()) { 1022 return null; 1023 } 1024 1025 if (associatedActivityToken == null || overlayToken == null) { 1026 return null; 1027 } 1028 1029 final TaskFragmentContainer.OverlayContainerRestoreParams params = 1030 mOverlayRestoreParams.get(associatedActivityToken); 1031 if (params == null) { 1032 return null; 1033 } 1034 1035 if (params.mOverlayToken != overlayToken) { 1036 // Not the same overlay container, no need to restore. 1037 return null; 1038 } 1039 1040 final Activity associatedActivity = getActivity(associatedActivityToken); 1041 if (associatedActivity == null || associatedActivity.isFinishing()) { 1042 return null; 1043 } 1044 1045 return params; 1046 } 1047 1048 /** 1049 * Called when the {@link WindowContainerTransaction} created with 1050 * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. 1051 * 1052 * @param wct The {@link WindowContainerTransaction} to make any changes with if 1053 * needed. 1054 * @param errorCallbackToken token set in 1055 * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} 1056 * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no 1057 * TaskFragment created. 1058 * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed 1059 * transaction operation. 1060 * @param exception exception from the server side. 1061 */ 1062 // Suppress GuardedBy warning because lint ask to mark this method as 1063 // @GuardedBy(container.mController.mLock), which is mLock itself 1064 @SuppressWarnings("GuardedBy") 1065 @VisibleForTesting 1066 @GuardedBy("mLock") onTaskFragmentError(@onNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception)1067 void onTaskFragmentError(@NonNull WindowContainerTransaction wct, 1068 @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, 1069 @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) { 1070 Log.e(TAG, "onTaskFragmentError=" + exception.getMessage()); 1071 switch (opType) { 1072 case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: 1073 case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { 1074 final TaskFragmentContainer container; 1075 if (taskFragmentInfo != null) { 1076 container = getContainer(taskFragmentInfo.getFragmentToken()); 1077 } else { 1078 container = null; 1079 } 1080 if (container == null) { 1081 break; 1082 } 1083 1084 // Update the latest taskFragmentInfo and perform necessary clean-up 1085 container.setInfo(wct, taskFragmentInfo); 1086 container.clearPendingAppearedActivities(); 1087 if (container.isEmpty()) { 1088 mTransactionManager.getCurrentTransactionRecord() 1089 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 1090 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 1091 } 1092 break; 1093 } 1094 default: 1095 Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo 1096 + ", opType = " + opType); 1097 } 1098 } 1099 1100 /** 1101 * Called on receiving {@link #onTaskFragmentVanished} for cleanup. 1102 */ 1103 @GuardedBy("mLock") cleanupTaskFragment(@onNull IBinder taskFragmentToken)1104 private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { 1105 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1106 final TaskContainer taskContainer = mTaskContainers.valueAt(i); 1107 if (!taskContainer.mFinishedContainer.remove(taskFragmentToken)) { 1108 continue; 1109 } 1110 if (taskContainer.isEmpty()) { 1111 // Cleanup the TaskContainer if it becomes empty. 1112 mTaskContainers.remove(taskContainer.getTaskId()); 1113 mDividerPresenters.remove(taskContainer.getTaskId()); 1114 } 1115 return; 1116 } 1117 } 1118 1119 @VisibleForTesting 1120 @GuardedBy("mLock") onActivityCreated(@onNull WindowContainerTransaction wct, @NonNull Activity launchedActivity)1121 void onActivityCreated(@NonNull WindowContainerTransaction wct, 1122 @NonNull Activity launchedActivity) { 1123 resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */); 1124 updateCallbackIfNecessary(); 1125 } 1126 1127 /** 1128 * Checks if the new added activity should be routed to a particular container. It can create a 1129 * new container for the activity and a new split container if necessary. 1130 * 1131 * @param activity the activity that is newly added to the Task. 1132 * @param isOnReparent whether the activity is reparented to the Task instead of new launched. 1133 * We only support to split as primary for reparented activity for now. 1134 * @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or 1135 * in a state that the caller shouldn't handle. 1136 */ 1137 @VisibleForTesting 1138 @GuardedBy("mLock") resolveActivityToContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnReparent)1139 boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct, 1140 @NonNull Activity activity, boolean isOnReparent) { 1141 if (isInPictureInPicture(activity) || activity.isFinishing()) { 1142 // We don't embed activity when it is in PIP, or finishing. Return true since we don't 1143 // want any extra handling. 1144 return true; 1145 } 1146 1147 final TaskFragmentContainer container = getContainerWithActivity(activity); 1148 if (!isOnReparent && container == null 1149 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) { 1150 // We can't find the new launched activity in any recorded container, but it is 1151 // currently placed in an embedded TaskFragment. This can happen in two cases: 1152 // 1. the activity is embedded in another app. 1153 // 2. the organizer has already requested to remove the TaskFragment. 1154 // In either case, return true since we don't want any extra handling. 1155 Log.d(TAG, "Activity is in a TaskFragment that is not recorded by the organizer. r=" 1156 + activity); 1157 return true; 1158 } 1159 1160 if (container != null && container.shouldSkipActivityResolving()) { 1161 return true; 1162 } 1163 1164 final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null; 1165 if (!isOnReparent && taskContainer != null 1166 && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */) 1167 != container) { 1168 // Do not resolve if the launched activity is not the top-most container (excludes 1169 // the pinned and overlay container) in the Task. 1170 return true; 1171 } 1172 1173 // Ensure the top TaskFragments are updated to the right config if activity is resolved 1174 // to a new TaskFragment while pin TF exists. 1175 final boolean handled = resolveActivityToContainerByRule(wct, activity, container, 1176 isOnReparent); 1177 if (handled && taskContainer != null) { 1178 final SplitPinContainer splitPinContainer = taskContainer.getSplitPinContainer(); 1179 if (splitPinContainer != null) { 1180 final TaskFragmentContainer resolvedContainer = getContainerWithActivity(activity); 1181 if (resolvedContainer != null && resolvedContainer.getRunningActivityCount() <= 1) { 1182 updateContainer(wct, splitPinContainer.getSecondaryContainer()); 1183 } 1184 } 1185 } 1186 return handled; 1187 } 1188 1189 /** 1190 * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules. 1191 */ 1192 @GuardedBy("mLock") resolveActivityToContainerByRule(@onNull WindowContainerTransaction wct, @NonNull Activity activity, @Nullable TaskFragmentContainer container, boolean isOnReparent)1193 boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct, 1194 @NonNull Activity activity, @Nullable TaskFragmentContainer container, 1195 boolean isOnReparent) { 1196 /* 1197 * We will check the following to see if there is any embedding rule matched: 1198 * 1. Whether the new launched activity should always expand. 1199 * 2. Whether the new launched activity should launch a placeholder. 1200 * 3. Whether the new launched activity has already been in a split with a rule matched 1201 * (likely done in #onStartActivity). 1202 * 4. Whether the activity below (if any) should be split with the new launched activity. 1203 * 5. Whether the activity split with the activity below (if any) should be split with the 1204 * new launched activity. 1205 */ 1206 1207 // 1. Whether the new launched activity should always expand. 1208 if (shouldExpand(activity, null /* intent */)) { 1209 expandActivity(wct, activity); 1210 return true; 1211 } 1212 1213 // 2. Whether the new launched activity should launch a placeholder. 1214 if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) { 1215 return true; 1216 } 1217 1218 // Skip resolving the following split-rules if the launched activity has been requested 1219 // to be launched into its current container. 1220 if (container != null && container.isActivityInRequestedTaskFragment( 1221 activity.getActivityToken())) { 1222 return true; 1223 } 1224 1225 // 3. Whether the new launched activity has already been in a split with a rule matched. 1226 if (isNewActivityInSplitWithRuleMatched(activity)) { 1227 return true; 1228 } 1229 1230 // 4. Whether the activity below (if any) should be split with the new launched activity. 1231 final Activity activityBelow = findActivityBelow(activity); 1232 if (activityBelow == null) { 1233 // Can't find any activity below. 1234 return false; 1235 } 1236 if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) { 1237 // Have split rule of [ activityBelow | launchedActivity ]. 1238 return true; 1239 } 1240 if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) { 1241 // Have split rule of [ launchedActivity | activityBelow]. 1242 return true; 1243 } 1244 1245 // 5. Whether the activity split with the activity below (if any) should be split with the 1246 // new launched activity. 1247 final TaskFragmentContainer activityBelowContainer = getContainerWithActivity( 1248 activityBelow); 1249 final SplitContainer topSplit = getActiveSplitForContainer(activityBelowContainer); 1250 if (topSplit == null || !isTopMostSplit(topSplit)) { 1251 // Skip if it is not the topmost split. 1252 return false; 1253 } 1254 final TaskFragmentContainer otherTopContainer = 1255 topSplit.getPrimaryContainer() == activityBelowContainer 1256 ? topSplit.getSecondaryContainer() 1257 : topSplit.getPrimaryContainer(); 1258 final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity(); 1259 if (otherTopActivity == null || otherTopActivity == activity) { 1260 // Can't find the top activity on the other split TaskFragment. 1261 return false; 1262 } 1263 if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) { 1264 // Have split rule of [ otherTopActivity | launchedActivity ]. 1265 return true; 1266 } 1267 // Have split rule of [ launchedActivity | otherTopActivity]. 1268 return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity); 1269 } 1270 1271 /** 1272 * Places the given activity to the top most TaskFragment in the task if there is any. 1273 */ 1274 @GuardedBy("mLock") 1275 @VisibleForTesting placeActivityInTopContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1276 void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct, 1277 @NonNull Activity activity) { 1278 if (getContainerWithActivity(activity) != null) { 1279 // The activity has already been put in a TaskFragment. This is likely to be done by 1280 // the server when the activity is started. 1281 return; 1282 } 1283 final int taskId = getTaskId(activity); 1284 final TaskContainer taskContainer = getTaskContainer(taskId); 1285 if (taskContainer == null) { 1286 return; 1287 } 1288 // TODO(b/301034784): Check if it makes sense to place the activity in overlay container. 1289 final TaskFragmentContainer targetContainer = 1290 taskContainer.getTopNonFinishingTaskFragmentContainer(); 1291 if (targetContainer == null) { 1292 return; 1293 } 1294 targetContainer.addPendingAppearedActivity(activity); 1295 wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), 1296 activity.getActivityToken()); 1297 } 1298 1299 /** 1300 * Starts an activity to side of the launchingActivity with the provided split config. 1301 */ 1302 // Suppress GuardedBy warning because lint ask to mark this method as 1303 // @GuardedBy(container.mController.mLock), which is mLock itself 1304 @SuppressWarnings("GuardedBy") 1305 @GuardedBy("mLock") startActivityToSide(@onNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder)1306 private void startActivityToSide(@NonNull WindowContainerTransaction wct, 1307 @NonNull Activity launchingActivity, @NonNull Intent intent, 1308 @Nullable Bundle options, @NonNull SplitRule sideRule, 1309 @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback, 1310 boolean isPlaceholder) { 1311 try { 1312 mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule, 1313 splitAttributes, isPlaceholder); 1314 } catch (Exception e) { 1315 if (failureCallback != null) { 1316 failureCallback.accept(e); 1317 } 1318 } 1319 } 1320 1321 /** 1322 * Expands the given activity by either expanding the TaskFragment it is currently in or putting 1323 * it into a new expanded TaskFragment. 1324 */ 1325 @GuardedBy("mLock") expandActivity(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1326 private void expandActivity(@NonNull WindowContainerTransaction wct, 1327 @NonNull Activity activity) { 1328 final TaskFragmentContainer container = getContainerWithActivity(activity); 1329 if (shouldContainerBeExpanded(container)) { 1330 // Make sure that the existing container is expanded. 1331 mPresenter.expandTaskFragment(wct, container); 1332 } else { 1333 // Put activity into a new expanded container. 1334 final TaskFragmentContainer newContainer = 1335 new TaskFragmentContainer.Builder(this, getTaskId(activity), activity) 1336 .setPendingAppearedActivity(activity).build(); 1337 mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity); 1338 } 1339 } 1340 1341 /** 1342 * Whether the given new launched activity is in a split with a rule matched. 1343 */ 1344 // Suppress GuardedBy warning because lint asks to mark this method as 1345 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1346 @SuppressWarnings("GuardedBy") 1347 @GuardedBy("mLock") isNewActivityInSplitWithRuleMatched(@onNull Activity launchedActivity)1348 private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) { 1349 final TaskFragmentContainer container = getContainerWithActivity(launchedActivity); 1350 final SplitContainer splitContainer = getActiveSplitForContainer(container); 1351 if (splitContainer == null) { 1352 return false; 1353 } 1354 1355 if (container == splitContainer.getPrimaryContainer()) { 1356 // The new launched can be in the primary container when it is starting a new activity 1357 // onCreate. 1358 final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); 1359 final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent(); 1360 if (secondaryIntent != null) { 1361 // Check with the pending Intent before it is started on the server side. 1362 // This can happen if the launched Activity start a new Intent to secondary during 1363 // #onCreated(). 1364 return getSplitRule(launchedActivity, secondaryIntent) != null; 1365 } 1366 final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity(); 1367 return secondaryActivity != null 1368 && getSplitRule(launchedActivity, secondaryActivity) != null; 1369 } 1370 1371 // Check if the new launched activity is a placeholder. 1372 if (splitContainer.getSplitRule() instanceof SplitPlaceholderRule) { 1373 final SplitPlaceholderRule placeholderRule = 1374 (SplitPlaceholderRule) splitContainer.getSplitRule(); 1375 final ComponentName placeholderName = placeholderRule.getPlaceholderIntent() 1376 .getComponent(); 1377 // TODO(b/232330767): Do we have a better way to check this? 1378 return placeholderName == null 1379 || placeholderName.equals(launchedActivity.getComponentName()) 1380 || placeholderRule.getPlaceholderIntent().equals(launchedActivity.getIntent()); 1381 } 1382 1383 // Check if the new launched activity should be split with the primary top activity. 1384 final Activity primaryActivity = splitContainer.getPrimaryContainer() 1385 .getTopNonFinishingActivity(); 1386 if (primaryActivity == null) { 1387 return false; 1388 } 1389 /* TODO(b/231845476) we should always respect clearTop. 1390 final SplitPairRule curSplitRule = (SplitPairRule) splitContainer.getSplitRule(); 1391 final SplitPairRule splitRule = getSplitRule(primaryActivity, launchedActivity); 1392 return splitRule != null && haveSamePresentation(splitRule, curSplitRule) 1393 // If the new launched split rule should clear top and it is not the bottom most, 1394 // it means we should create a new split pair and clear the existing secondary. 1395 && (!splitRule.shouldClearTop() 1396 || container.getBottomMostActivity() == launchedActivity); 1397 */ 1398 return getSplitRule(primaryActivity, launchedActivity) != null; 1399 } 1400 1401 /** 1402 * Finds the activity below the given activity. 1403 */ 1404 @VisibleForTesting 1405 @Nullable 1406 @GuardedBy("mLock") findActivityBelow(@onNull Activity activity)1407 Activity findActivityBelow(@NonNull Activity activity) { 1408 Activity activityBelow = null; 1409 final TaskFragmentContainer container = getContainerWithActivity(activity); 1410 // Looking for the activity below from the information we already have if the container 1411 // only embeds activities of the same process because activities of other processes are not 1412 // available in this embedding host process for security concern. 1413 if (container != null && !container.hasCrossProcessActivities()) { 1414 final List<Activity> containerActivities = container.collectNonFinishingActivities(); 1415 final int index = containerActivities.indexOf(activity); 1416 if (index > 0) { 1417 activityBelow = containerActivities.get(index - 1); 1418 } 1419 } 1420 if (activityBelow == null) { 1421 final IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow( 1422 activity.getActivityToken()); 1423 if (belowToken != null) { 1424 activityBelow = getActivity(belowToken); 1425 } 1426 } 1427 return activityBelow; 1428 } 1429 1430 /** 1431 * Checks if there is a rule to split the two activities. If there is one, puts them into split 1432 * and returns {@code true}. Otherwise, returns {@code false}. 1433 */ 1434 // Suppress GuardedBy warning because lint ask to mark this method as 1435 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1436 @SuppressWarnings("GuardedBy") 1437 @GuardedBy("mLock") putActivitiesIntoSplitIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity)1438 private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct, 1439 @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { 1440 final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity); 1441 if (splitRule == null) { 1442 return false; 1443 } 1444 final TaskFragmentContainer primaryContainer = getContainerWithActivity( 1445 primaryActivity); 1446 final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); 1447 final TaskContainer.TaskProperties taskProperties = mPresenter 1448 .getTaskProperties(primaryActivity); 1449 final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( 1450 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), 1451 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity)); 1452 if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() 1453 && canReuseContainer(splitRule, splitContainer.getSplitRule(), 1454 taskProperties.getTaskMetrics(), 1455 calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) { 1456 // Can launch in the existing secondary container if the rules share the same 1457 // presentation. 1458 final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); 1459 if (secondaryContainer == getContainerWithActivity(secondaryActivity)) { 1460 // The activity is already in the target TaskFragment. 1461 return true; 1462 } 1463 secondaryContainer.addPendingAppearedActivity(secondaryActivity); 1464 if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, 1465 secondaryActivity, null /* secondaryIntent */) 1466 != RESULT_EXPAND_FAILED_NO_TF_INFO) { 1467 wct.reparentActivityToTaskFragment( 1468 secondaryContainer.getTaskFragmentToken(), 1469 secondaryActivity.getActivityToken()); 1470 return true; 1471 } 1472 } 1473 // Create new split pair. 1474 mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule, 1475 calculatedSplitAttributes); 1476 return true; 1477 } 1478 1479 @GuardedBy("mLock") onActivityConfigurationChanged(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1480 private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct, 1481 @NonNull Activity activity) { 1482 if (activity.isFinishing()) { 1483 // Do nothing if the activity is currently finishing. 1484 return; 1485 } 1486 1487 if (isInPictureInPicture(activity)) { 1488 // We don't embed activity when it is in PIP. 1489 return; 1490 } 1491 final TaskFragmentContainer currentContainer = getContainerWithActivity(activity); 1492 1493 if (currentContainer != null) { 1494 // Changes to activities in controllers are handled in 1495 // onTaskFragmentParentInfoChanged 1496 return; 1497 } 1498 1499 // Check if activity requires a placeholder 1500 launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */); 1501 } 1502 1503 @GuardedBy("mLock") onActivityPaused(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1504 private void onActivityPaused(@NonNull WindowContainerTransaction wct, 1505 @NonNull Activity activity) { 1506 // Checks if there's any finishing activity in paused state associate with an overlay 1507 // container. #OnActivityPostDestroyed is a very late signal, which is called after activity 1508 // is not visible and the next activity shows on screen. 1509 if (!activity.isFinishing()) { 1510 // onPaused is triggered without finishing. Early return. 1511 return; 1512 } 1513 // Check if we should dismiss the overlay container with this finishing activity. 1514 final IBinder activityToken = activity.getActivityToken(); 1515 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1516 mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken); 1517 } 1518 1519 mOverlayRestoreParams.remove(activity.getActivityToken()); 1520 updateCallbackIfNecessary(); 1521 } 1522 1523 @VisibleForTesting 1524 @GuardedBy("mLock") onActivityDestroyed(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1525 void onActivityDestroyed(@NonNull WindowContainerTransaction wct, @NonNull Activity activity) { 1526 if (!activity.isFinishing()) { 1527 // onDestroyed is triggered without finishing. This happens when the activity is 1528 // relaunched. In this case, we don't want to cleanup the record. 1529 return; 1530 } 1531 // Remove any pending appeared activity, as the server won't send finished activity to the 1532 // organizer. 1533 final IBinder activityToken = activity.getActivityToken(); 1534 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1535 mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken); 1536 } 1537 1538 mOverlayRestoreParams.remove(activity.getActivityToken()); 1539 // We didn't trigger the callback if there were any pending appeared activities, so check 1540 // again after the pending is removed. 1541 updateCallbackIfNecessary(); 1542 } 1543 1544 /** 1545 * Called when we have been waiting too long for the TaskFragment to become non-empty after 1546 * creation. 1547 */ 1548 @GuardedBy("mLock") onTaskFragmentAppearEmptyTimeout(@onNull TaskFragmentContainer container)1549 void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) { 1550 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 1551 onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container); 1552 // Can be applied independently as a timeout callback. 1553 transactionRecord.apply(true /* shouldApplyIndependently */); 1554 } 1555 1556 /** 1557 * Called when we have been waiting too long for the TaskFragment to become non-empty after 1558 * creation. 1559 */ 1560 @GuardedBy("mLock") onTaskFragmentAppearEmptyTimeout(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1561 void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, 1562 @NonNull TaskFragmentContainer container) { 1563 mTransactionManager.getCurrentTransactionRecord() 1564 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 1565 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 1566 } 1567 1568 @Nullable 1569 @GuardedBy("mLock") resolveStartActivityIntentFromNonActivityContext( @onNull WindowContainerTransaction wct, @NonNull Intent intent)1570 private TaskFragmentContainer resolveStartActivityIntentFromNonActivityContext( 1571 @NonNull WindowContainerTransaction wct, @NonNull Intent intent) { 1572 final int taskCount = mTaskContainers.size(); 1573 if (taskCount == 0) { 1574 // We don't have other Activity to check split with. 1575 return null; 1576 } 1577 if (taskCount > 1) { 1578 Log.w(TAG, "App is calling startActivity from a non-Activity context when it has" 1579 + " more than one Task. If the new launch Activity is in a different process," 1580 + " and it is expected to be embedded, please start it from an Activity" 1581 + " instead."); 1582 return null; 1583 } 1584 1585 // Check whether the Intent should be embedded in the known Task. 1586 final TaskContainer taskContainer = mTaskContainers.valueAt(0); 1587 if (taskContainer.isInPictureInPicture() 1588 || taskContainer.getTopNonFinishingActivity(false /* includeOverlay */) == null) { 1589 // We don't embed activity when it is in PIP, or if we can't find any other owner 1590 // activity in non-overlay container in the Task. 1591 return null; 1592 } 1593 1594 return resolveStartActivityIntent(wct, taskContainer.getTaskId(), intent, 1595 null /* launchingActivity */); 1596 } 1597 1598 /** 1599 * When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer} 1600 * that we should reparent the new activity to if there is any embedding rule matched. 1601 * 1602 * @param wct {@link WindowContainerTransaction} including all the window change 1603 * requests. The caller is responsible to call 1604 * {@link android.window.TaskFragmentOrganizer#applyTransaction}. 1605 * @param taskId The Task to start the activity in. 1606 * @param intent The {@link Intent} for starting the new launched activity. 1607 * @param launchingActivity The {@link Activity} that starts the new activity. We will 1608 * prioritize to split the new activity with it if it is not 1609 * {@code null}. 1610 * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there 1611 * is no embedding rule matched. 1612 */ 1613 @VisibleForTesting 1614 @Nullable 1615 @GuardedBy("mLock") resolveStartActivityIntent(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity)1616 TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, 1617 int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { 1618 if (launchingActivity != null) { 1619 final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity( 1620 launchingActivity); 1621 if (taskFragmentContainer != null 1622 && taskFragmentContainer.shouldSkipActivityResolving()) { 1623 return null; 1624 } 1625 if (isAssociatedWithOverlay(launchingActivity)) { 1626 // Skip resolving if the launching activity associated with an overlay. 1627 return null; 1628 } 1629 } 1630 1631 // Ensure the top TaskFragments are updated to the right config if the intent is resolved 1632 // to a new TaskFragment while pin TF exists. 1633 final TaskFragmentContainer launchingContainer = resolveStartActivityIntentByRule(wct, 1634 taskId, intent, launchingActivity); 1635 if (launchingContainer != null && launchingContainer.getRunningActivityCount() == 0) { 1636 final SplitPinContainer splitPinContainer = 1637 launchingContainer.getTaskContainer().getSplitPinContainer(); 1638 if (splitPinContainer != null) { 1639 updateContainer(wct, splitPinContainer.getSecondaryContainer()); 1640 } 1641 } 1642 return launchingContainer; 1643 } 1644 1645 /** 1646 * Resolves the intent to a {@link TaskFragmentContainer} according to the Split-rules. 1647 */ 1648 @Nullable resolveStartActivityIntentByRule(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity)1649 TaskFragmentContainer resolveStartActivityIntentByRule(@NonNull WindowContainerTransaction wct, 1650 int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { 1651 /* 1652 * We will check the following to see if there is any embedding rule matched: 1653 * 1. Whether the new activity intent should always expand. 1654 * 2. Whether the launching activity (if set) should be split with the new activity intent. 1655 * 3. Whether the top activity (if any) should be split with the new activity intent. 1656 * 4. Whether the top activity (if any) in other split should be split with the new 1657 * activity intent. 1658 */ 1659 1660 // 1. Whether the new activity intent should always expand. 1661 if (shouldExpand(null /* activity */, intent)) { 1662 return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity); 1663 } 1664 1665 // 2. Whether the launching activity (if set) should be split with the new activity intent. 1666 if (launchingActivity != null) { 1667 final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, 1668 launchingActivity, intent, true /* respectClearTop */); 1669 if (container != null) { 1670 return container; 1671 } 1672 } 1673 1674 // 3. Whether the top activity (if any) should be split with the new activity intent. 1675 final TaskContainer taskContainer = getTaskContainer(taskId); 1676 if (taskContainer == null 1677 || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) { 1678 // There is no other activity in the Task to check split with. 1679 return null; 1680 } 1681 final TaskFragmentContainer topContainer = 1682 taskContainer.getTopNonFinishingTaskFragmentContainer(); 1683 final Activity topActivity = topContainer.getTopNonFinishingActivity(); 1684 if (topActivity != null && topActivity != launchingActivity) { 1685 final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, 1686 topActivity, intent, false /* respectClearTop */); 1687 if (container != null) { 1688 return container; 1689 } 1690 } 1691 1692 // 4. Whether the top activity (if any) in other split should be split with the new 1693 // activity intent. 1694 final SplitContainer topSplit = getActiveSplitForContainer(topContainer); 1695 if (topSplit == null) { 1696 return null; 1697 } 1698 final TaskFragmentContainer otherTopContainer = 1699 topSplit.getPrimaryContainer() == topContainer 1700 ? topSplit.getSecondaryContainer() 1701 : topSplit.getPrimaryContainer(); 1702 final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity(); 1703 if (otherTopActivity != null && otherTopActivity != launchingActivity) { 1704 return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent, 1705 false /* respectClearTop */); 1706 } 1707 return null; 1708 } 1709 1710 /** 1711 * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into. 1712 */ 1713 @GuardedBy("mLock") 1714 @Nullable createEmptyExpandedContainer( @onNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity)1715 private TaskFragmentContainer createEmptyExpandedContainer( 1716 @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, 1717 @Nullable Activity launchingActivity) { 1718 return createEmptyContainer(wct, intent, taskId, 1719 new ActivityStackAttributes.Builder().build(), launchingActivity, 1720 null /* overlayTag */, null /* launchOptions */, 1721 false /* shouldAssociateWithLaunchingActivity */); 1722 } 1723 1724 /** 1725 * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into. 1726 * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an 1727 * overlay container. 1728 */ 1729 @VisibleForTesting 1730 @GuardedBy("mLock") 1731 @Nullable createEmptyContainer( @onNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @NonNull ActivityStackAttributes activityStackAttributes, @Nullable Activity launchingActivity, @Nullable String overlayTag, @Nullable Bundle launchOptions, boolean associateLaunchingActivity)1732 TaskFragmentContainer createEmptyContainer( 1733 @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, 1734 @NonNull ActivityStackAttributes activityStackAttributes, 1735 @Nullable Activity launchingActivity, @Nullable String overlayTag, 1736 @Nullable Bundle launchOptions, boolean associateLaunchingActivity) { 1737 // We need an activity in the organizer process in the same Task to use as the owner 1738 // activity, as well as to get the Task window info. 1739 final Activity activityInTask; 1740 if (launchingActivity != null) { 1741 activityInTask = launchingActivity; 1742 } else { 1743 final TaskContainer taskContainer = getTaskContainer(taskId); 1744 activityInTask = taskContainer != null 1745 ? taskContainer.getTopNonFinishingActivity(true /* includeOverlay */) 1746 : null; 1747 } 1748 if (activityInTask == null) { 1749 // Can't find any activity in the Task that we can use as the owner activity. 1750 return null; 1751 } 1752 final TaskFragmentContainer container = 1753 new TaskFragmentContainer.Builder(this, taskId, activityInTask) 1754 .setPendingAppearedIntent(intent) 1755 .setOverlayTag(overlayTag) 1756 .setLaunchOptions(launchOptions) 1757 .setAssociatedActivity(associateLaunchingActivity ? activityInTask : null) 1758 .build(); 1759 final IBinder taskFragmentToken = container.getTaskFragmentToken(); 1760 // Note that taskContainer will not exist before calling #newContainer if the container 1761 // is the first embedded TF in the task. 1762 final TaskContainer taskContainer = container.getTaskContainer(); 1763 // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken. 1764 final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(), 1765 getMinDimensions(intent), container); 1766 final int windowingMode = taskContainer 1767 .getWindowingModeForTaskFragment(sanitizedBounds); 1768 mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(), 1769 sanitizedBounds, windowingMode); 1770 mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes, 1771 getMinDimensions(intent)); 1772 1773 return container; 1774 } 1775 1776 /** 1777 * Returns a container for the new activity intent to launch into as splitting with the primary 1778 * activity. 1779 */ 1780 @GuardedBy("mLock") 1781 @Nullable getSecondaryContainerForSplitIfAny( @onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent intent, boolean respectClearTop)1782 private TaskFragmentContainer getSecondaryContainerForSplitIfAny( 1783 @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, 1784 @NonNull Intent intent, boolean respectClearTop) { 1785 final SplitPairRule splitRule = getSplitRule(primaryActivity, intent); 1786 if (splitRule == null) { 1787 return null; 1788 } 1789 final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); 1790 final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); 1791 final TaskContainer.TaskProperties taskProperties = mPresenter 1792 .getTaskProperties(primaryActivity); 1793 final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics(); 1794 final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( 1795 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), 1796 getActivityIntentMinDimensionsPair(primaryActivity, intent)); 1797 if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() 1798 && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics, 1799 calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes()) 1800 // TODO(b/231845476) we should always respect clearTop. 1801 || !respectClearTop) 1802 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, 1803 null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) { 1804 // Can launch in the existing secondary container if the rules share the same 1805 // presentation. 1806 return splitContainer.getSecondaryContainer(); 1807 } 1808 // Create a new TaskFragment to split with the primary activity for the new activity. 1809 return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, 1810 splitRule, calculatedSplitAttributes); 1811 } 1812 1813 /** 1814 * Returns a container that this activity is registered with. An activity can only belong to one 1815 * container, or no container at all. 1816 */ 1817 @GuardedBy("mLock") 1818 @Nullable getContainerWithActivity(@onNull Activity activity)1819 TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) { 1820 return getContainerWithActivity(activity.getActivityToken()); 1821 } 1822 1823 @GuardedBy("mLock") 1824 @Nullable getContainerWithActivity(@onNull IBinder activityToken)1825 TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { 1826 for (int i = mTaskContainers.size() - 1; i >= 0; --i) { 1827 final TaskFragmentContainer container = mTaskContainers.valueAt(i) 1828 .getContainerWithActivity(activityToken); 1829 if (container != null) { 1830 return container; 1831 } 1832 } 1833 return null; 1834 } 1835 1836 /** 1837 * Creates and registers a new split with the provided containers and configuration. Finishes 1838 * existing secondary containers if found for the given primary container. 1839 */ 1840 // Suppress GuardedBy warning because lint ask to mark this method as 1841 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1842 @SuppressWarnings("GuardedBy") 1843 @GuardedBy("mLock") registerSplit(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes)1844 void registerSplit(@NonNull WindowContainerTransaction wct, 1845 @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, 1846 @NonNull TaskFragmentContainer secondaryContainer, 1847 @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) { 1848 final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, 1849 secondaryContainer, splitRule, splitAttributes); 1850 // Remove container later to prevent pinning escaping toast showing in lock task mode. 1851 if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { 1852 removeExistingSecondaryContainers(wct, primaryContainer); 1853 } 1854 primaryContainer.getTaskContainer().addSplitContainer(splitContainer); 1855 } 1856 1857 /** 1858 * Cleanups all the dependencies when the TaskFragment is entering PIP. 1859 */ 1860 @GuardedBy("mLock") cleanupForEnterPip(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1861 private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct, 1862 @NonNull TaskFragmentContainer container) { 1863 final TaskContainer taskContainer = container.getTaskContainer(); 1864 if (taskContainer == null) { 1865 return; 1866 } 1867 final List<SplitContainer> splitsToRemove = new ArrayList<>(); 1868 final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); 1869 final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>(); 1870 for (SplitContainer splitContainer : splitContainers) { 1871 if (splitContainer.getPrimaryContainer() != container 1872 && splitContainer.getSecondaryContainer() != container) { 1873 continue; 1874 } 1875 splitsToRemove.add(splitContainer); 1876 final TaskFragmentContainer splitTf = splitContainer.getPrimaryContainer() == container 1877 ? splitContainer.getSecondaryContainer() 1878 : splitContainer.getPrimaryContainer(); 1879 containersToUpdate.add(splitTf); 1880 // We don't want the PIP TaskFragment to be removed as a result of any of its dependents 1881 // being removed. 1882 splitTf.removeContainerToFinishOnExit(container); 1883 if (container.getTopNonFinishingActivity() != null) { 1884 splitTf.removeActivityToFinishOnExit(container.getTopNonFinishingActivity()); 1885 } 1886 } 1887 container.resetDependencies(); 1888 taskContainer.removeSplitContainers(splitsToRemove); 1889 // If there is any TaskFragment split with the PIP TaskFragment, update their presentations 1890 // since the split is dismissed. 1891 // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. 1892 for (TaskFragmentContainer containerToUpdate : containersToUpdate) { 1893 updateContainer(wct, containerToUpdate); 1894 } 1895 } 1896 1897 /** 1898 * Removes the container from bookkeeping records. 1899 */ removeContainer(@onNull TaskFragmentContainer container)1900 void removeContainer(@NonNull TaskFragmentContainer container) { 1901 removeContainers(container.getTaskContainer(), Collections.singletonList(container)); 1902 } 1903 1904 /** 1905 * Removes containers from bookkeeping records. 1906 */ removeContainers(@onNull TaskContainer taskContainer, @NonNull List<TaskFragmentContainer> containers)1907 void removeContainers(@NonNull TaskContainer taskContainer, 1908 @NonNull List<TaskFragmentContainer> containers) { 1909 // Remove all split containers that included this one 1910 taskContainer.removeTaskFragmentContainers(containers); 1911 // Marked as a pending removal which will be removed after it is actually removed on the 1912 // server side (#onTaskFragmentVanished). 1913 // In this way, we can keep track of the Task bounds until we no longer have any 1914 // TaskFragment there. 1915 taskContainer.mFinishedContainer.addAll(containers.stream().map( 1916 TaskFragmentContainer::getTaskFragmentToken).toList()); 1917 1918 // Cleanup any split references. 1919 final List<SplitContainer> containersToRemove = new ArrayList<>(); 1920 final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); 1921 for (SplitContainer splitContainer : splitContainers) { 1922 if (containersToRemove.contains(splitContainer)) { 1923 // Don't need to check because it has been in the remove list. 1924 continue; 1925 } 1926 if (containers.stream().anyMatch(container -> 1927 splitContainer.getPrimaryContainer().equals(container) 1928 || splitContainer.getSecondaryContainer().equals(container))) { 1929 containersToRemove.add(splitContainer); 1930 } 1931 } 1932 taskContainer.removeSplitContainers(containersToRemove); 1933 1934 // Cleanup any dependent references. 1935 final List<TaskFragmentContainer> taskFragmentContainers = 1936 taskContainer.getTaskFragmentContainers(); 1937 for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) { 1938 containerToUpdate.removeContainersToFinishOnExit(containers); 1939 } 1940 } 1941 1942 /** 1943 * Removes a secondary container for the given primary container if an existing split is 1944 * already registered. 1945 */ 1946 // Suppress GuardedBy warning because lint asks to mark this method as 1947 // @GuardedBy(existingSplitContainer.getSecondaryContainer().mController.mLock), which is mLock 1948 // itself 1949 @SuppressWarnings("GuardedBy") 1950 @GuardedBy("mLock") removeExistingSecondaryContainers(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer)1951 private void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct, 1952 @NonNull TaskFragmentContainer primaryContainer) { 1953 // If the primary container was already in a split - remove the secondary container that 1954 // is now covered by the new one that replaced it. 1955 final SplitContainer existingSplitContainer = getActiveSplitForContainer( 1956 primaryContainer); 1957 if (existingSplitContainer == null 1958 || primaryContainer == existingSplitContainer.getSecondaryContainer()) { 1959 return; 1960 } 1961 1962 // If the secondary container is pinned, it should not be removed. 1963 final SplitContainer activeContainer = 1964 getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer()); 1965 if (activeContainer instanceof SplitPinContainer) { 1966 return; 1967 } 1968 1969 existingSplitContainer.getSecondaryContainer().finish( 1970 false /* shouldFinishDependent */, mPresenter, wct, this); 1971 } 1972 1973 /** 1974 * Updates the presentation of the container. If the container is part of the split or should 1975 * have a placeholder, it will also update the other part of the split. 1976 */ 1977 @GuardedBy("mLock") updateContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1978 void updateContainer(@NonNull WindowContainerTransaction wct, 1979 @NonNull TaskFragmentContainer container) { 1980 if (!container.getTaskContainer().isVisible()) { 1981 // Wait until the Task is visible to avoid unnecessary update when the Task is still in 1982 // background. 1983 return; 1984 } 1985 1986 if (container.isOverlay()) { 1987 updateOverlayContainer(wct, container); 1988 return; 1989 } 1990 1991 if (launchPlaceholderIfNecessary(wct, container)) { 1992 // Placeholder was launched, the positions will be updated when the activity is added 1993 // to the secondary container. 1994 return; 1995 } 1996 if (shouldContainerBeExpanded(container)) { 1997 if (container.getInfo() != null) { 1998 mPresenter.expandTaskFragment(wct, container); 1999 } 2000 // If the info is not available yet the task fragment will be expanded when it's ready 2001 return; 2002 } 2003 final SplitContainer splitContainer = getActiveSplitForContainer(container); 2004 if (splitContainer == null) { 2005 return; 2006 } 2007 2008 updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */); 2009 } 2010 2011 2012 @VisibleForTesting 2013 // Suppress GuardedBy warning because lint ask to mark this method as 2014 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 2015 @SuppressWarnings("GuardedBy") 2016 @GuardedBy("mLock") updateOverlayContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)2017 void updateOverlayContainer(@NonNull WindowContainerTransaction wct, 2018 @NonNull TaskFragmentContainer container) { 2019 final TaskContainer taskContainer = container.getTaskContainer(); 2020 2021 if (dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer)) { 2022 return; 2023 } 2024 2025 if (mActivityStackAttributesCalculator == null) { 2026 Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container" 2027 + " can not be updated."); 2028 return; 2029 } 2030 2031 if (mActivityStackAttributesCalculator != null) { 2032 final ActivityStackAttributesCalculatorParams params = 2033 new ActivityStackAttributesCalculatorParams( 2034 mPresenter.createParentContainerInfoFromTaskProperties( 2035 taskContainer.getTaskProperties()), 2036 container.getOverlayTag(), 2037 container.getLaunchOptions()); 2038 final ActivityStackAttributes attributes = mActivityStackAttributesCalculator 2039 .apply(params); 2040 mPresenter.applyActivityStackAttributes(wct, container, attributes, 2041 container.getMinDimensions()); 2042 } 2043 } 2044 2045 /** 2046 * Dismisses {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the {@code taskContainer} 2047 * if needed. 2048 */ 2049 @GuardedBy("mLock") dismissAlwaysOnTopOverlayIfNeeded(@onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)2050 private boolean dismissAlwaysOnTopOverlayIfNeeded(@NonNull WindowContainerTransaction wct, 2051 @NonNull TaskContainer taskContainer) { 2052 // Dismiss always-on-top overlay container if it's the only container in the task and 2053 // there's no direct activity in the parent task. 2054 final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); 2055 if (containers.size() != 1 || taskContainer.hasDirectActivity()) { 2056 return false; 2057 } 2058 2059 final TaskFragmentContainer container = containers.getLast(); 2060 if (!container.isAlwaysOnTopOverlay()) { 2061 return false; 2062 } 2063 2064 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependant */); 2065 return true; 2066 } 2067 2068 /** 2069 * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the 2070 * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes} 2071 * are {@code null}, the {@link SplitAttributes} will be calculated with 2072 * {@link SplitPresenter#computeSplitAttributes}. 2073 * 2074 * @param splitContainer The {@link SplitContainer} to update 2075 * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}. 2076 * Otherwise, use the value calculated by 2077 * {@link SplitPresenter#computeSplitAttributes} 2078 * @return {@code true} if the update succeed. Otherwise, returns {@code false}. 2079 */ 2080 @VisibleForTesting 2081 @GuardedBy("mLock") updateSplitContainerIfNeeded(@onNull SplitContainer splitContainer, @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes)2082 boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer, 2083 @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) { 2084 if (!isTopMostSplit(splitContainer)) { 2085 // Skip position update - it isn't the topmost split. 2086 return false; 2087 } 2088 if (splitContainer.getPrimaryContainer().isFinished() 2089 || splitContainer.getSecondaryContainer().isFinished()) { 2090 // Skip position update - one or both containers are finished. 2091 return false; 2092 } 2093 if (splitAttributes == null) { 2094 final TaskContainer.TaskProperties taskProperties = splitContainer.getTaskContainer() 2095 .getTaskProperties(); 2096 final SplitRule splitRule = splitContainer.getSplitRule(); 2097 final SplitAttributes defaultSplitAttributes = splitContainer 2098 .getDefaultSplitAttributes(); 2099 final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); 2100 splitAttributes = mPresenter.computeSplitAttributes(taskProperties, splitRule, 2101 defaultSplitAttributes, minDimensionsPair); 2102 } 2103 splitContainer.updateCurrentSplitAttributes(splitAttributes); 2104 if (dismissPlaceholderIfNecessary(wct, splitContainer)) { 2105 // Placeholder was finished, the positions will be updated when its container is emptied 2106 return true; 2107 } 2108 mPresenter.updateSplitContainer(splitContainer, wct); 2109 return true; 2110 } 2111 2112 /** 2113 * Whether the given split is the topmost split in the Task. 2114 */ isTopMostSplit(@onNull SplitContainer splitContainer)2115 private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) { 2116 final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer() 2117 .getTaskContainer().getSplitContainers(); 2118 return splitContainer == splitContainers.get(splitContainers.size() - 1); 2119 } 2120 2121 /** 2122 * Returns the top active split container that has the provided container, if available. 2123 */ 2124 @Nullable getActiveSplitForContainer(@ullable TaskFragmentContainer container)2125 private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) { 2126 if (container == null) { 2127 return null; 2128 } 2129 return container.getTaskContainer().getActiveSplitForContainer(container); 2130 } 2131 2132 /** 2133 * Returns the active split that has the provided containers as primary and secondary or as 2134 * secondary and primary, if available. 2135 */ 2136 @GuardedBy("mLock") 2137 @Nullable getActiveSplitForContainers( @onNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer)2138 SplitContainer getActiveSplitForContainers( 2139 @NonNull TaskFragmentContainer firstContainer, 2140 @NonNull TaskFragmentContainer secondContainer) { 2141 final List<SplitContainer> splitContainers = firstContainer.getTaskContainer() 2142 .getSplitContainers(); 2143 for (int i = splitContainers.size() - 1; i >= 0; i--) { 2144 final SplitContainer splitContainer = splitContainers.get(i); 2145 final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); 2146 final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer(); 2147 if ((firstContainer == secondary && secondContainer == primary) 2148 || (firstContainer == primary && secondContainer == secondary)) { 2149 return splitContainer; 2150 } 2151 } 2152 return null; 2153 } 2154 2155 /** 2156 * Checks if the container requires a placeholder and launches it if necessary. 2157 */ 2158 @GuardedBy("mLock") launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)2159 private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, 2160 @NonNull TaskFragmentContainer container) { 2161 final Activity topActivity = container.getTopNonFinishingActivity(); 2162 if (topActivity == null) { 2163 return false; 2164 } 2165 2166 return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */); 2167 } 2168 2169 // Suppress GuardedBy warning because lint ask to mark this method as 2170 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 2171 @SuppressWarnings("GuardedBy") 2172 @GuardedBy("mLock") launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnCreated)2173 boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, 2174 @NonNull Activity activity, boolean isOnCreated) { 2175 if (activity.isFinishing()) { 2176 return false; 2177 } 2178 2179 if (isAssociatedWithOverlay(activity)) { 2180 // Can't launch the placeholder if the activity associates an overlay. 2181 return false; 2182 } 2183 2184 final TaskFragmentContainer container = getContainerWithActivity(activity); 2185 if (container != null && !allowLaunchPlaceholder(container)) { 2186 // We don't allow activity in this TaskFragment to launch placeholder. 2187 return false; 2188 } 2189 2190 // Check if there is enough space for launch 2191 final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity); 2192 2193 if (placeholderRule == null) { 2194 return false; 2195 } 2196 2197 if (container != null && container.getTaskContainer().isPlaceholderRuleSuppressed()) { 2198 return false; 2199 } 2200 2201 final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity); 2202 final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, 2203 placeholderRule.getPlaceholderIntent()); 2204 final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties, 2205 placeholderRule, placeholderRule.getDefaultSplitAttributes(), minDimensionsPair); 2206 if (!SplitPresenter.shouldShowSplit(splitAttributes)) { 2207 return false; 2208 } 2209 2210 // TODO(b/190433398): Handle failed request 2211 final Bundle options = getPlaceholderOptions(activity, isOnCreated); 2212 startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options, 2213 placeholderRule, splitAttributes, null /* failureCallback */, 2214 true /* isPlaceholder */); 2215 return true; 2216 } 2217 2218 /** 2219 * Whether or not to allow activity in this container to launch placeholder. 2220 */ 2221 @GuardedBy("mLock") allowLaunchPlaceholder(@onNull TaskFragmentContainer container)2222 private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) { 2223 if (container.isOverlay()) { 2224 // Don't launch placeholder if the container is an overlay. 2225 return false; 2226 } 2227 2228 final TaskFragmentContainer topContainer = container.getTaskContainer() 2229 .getTopNonFinishingTaskFragmentContainer(); 2230 if (container != topContainer) { 2231 // The container is not the top most. 2232 if (!container.isVisible()) { 2233 // In case the container is visible (the one on top may be transparent), we may 2234 // still want to launch placeholder even if it is not the top most. 2235 return false; 2236 } 2237 if (topContainer.isWaitingActivityAppear()) { 2238 // When the top container appeared info is not sent by the server yet, the visible 2239 // check above may not be reliable. 2240 return false; 2241 } 2242 } 2243 2244 final SplitContainer splitContainer = getActiveSplitForContainer(container); 2245 if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { 2246 // Don't launch placeholder for primary split container. 2247 return false; 2248 } 2249 if (splitContainer instanceof SplitPinContainer) { 2250 // Don't launch placeholder if pinned 2251 return false; 2252 } 2253 return true; 2254 } 2255 2256 /** 2257 * Gets the activity options for starting the placeholder activity. In case the placeholder is 2258 * launched when the Task is in the background, we don't want to bring the Task to the front. 2259 * 2260 * @param primaryActivity the primary activity to launch the placeholder from. 2261 * @param isOnCreated whether this happens during the primary activity onCreated. 2262 */ 2263 @VisibleForTesting 2264 @GuardedBy("mLock") 2265 @Nullable getPlaceholderOptions(@onNull Activity primaryActivity, boolean isOnCreated)2266 Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) { 2267 // Setting avoid move to front will also skip the animation. We only want to do that when 2268 // the Task is currently in background. 2269 // Check if the primary is resumed or if this is called when the primary is onCreated 2270 // (not resumed yet). 2271 if (isOnCreated || primaryActivity.isResumed()) { 2272 // Only set trigger type if the launch happens in foreground. 2273 mTransactionManager.getCurrentTransactionRecord() 2274 .setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); 2275 return null; 2276 } 2277 final ActivityOptions options = ActivityOptions.makeBasic(); 2278 options.setAvoidMoveToFront(); 2279 return options.toBundle(); 2280 } 2281 2282 // Suppress GuardedBy warning because lint ask to mark this method as 2283 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 2284 @SuppressWarnings("GuardedBy") 2285 @VisibleForTesting 2286 @GuardedBy("mLock") dismissPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull SplitContainer splitContainer)2287 boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, 2288 @NonNull SplitContainer splitContainer) { 2289 if (!splitContainer.isPlaceholderContainer()) { 2290 return false; 2291 } 2292 2293 if (isStickyPlaceholderRule(splitContainer.getSplitRule())) { 2294 // The placeholder should remain after it was first shown. 2295 return false; 2296 } 2297 final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes(); 2298 if (SplitPresenter.shouldShowSplit(splitAttributes)) { 2299 return false; 2300 } 2301 if (SplitPresenter.shouldShowPlaceholderWhenExpanded(splitAttributes)) { 2302 return false; 2303 } 2304 2305 mTransactionManager.getCurrentTransactionRecord() 2306 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 2307 mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), 2308 false /* shouldFinishDependent */); 2309 return true; 2310 } 2311 2312 /** 2313 * Returns the rule to launch a placeholder for the activity with the provided component name 2314 * if it is configured in the split config. 2315 */ 2316 @GuardedBy("mLock") getPlaceholderRule(@onNull Activity activity)2317 private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) { 2318 for (EmbeddingRule rule : mSplitRules) { 2319 if (!(rule instanceof SplitPlaceholderRule)) { 2320 continue; 2321 } 2322 SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule; 2323 if (placeholderRule.matchesActivity(activity)) { 2324 return placeholderRule; 2325 } 2326 } 2327 return null; 2328 } 2329 2330 /** 2331 * Notifies listeners about changes to split states if necessary. 2332 */ 2333 @VisibleForTesting 2334 @GuardedBy("mLock") updateCallbackIfNecessary()2335 void updateCallbackIfNecessary() { 2336 updateSplitInfoCallbackIfNecessary(); 2337 updateActivityStackCallbackIfNecessary(); 2338 } 2339 2340 /** 2341 * Notifies callbacks about changes to split states if necessary. 2342 */ 2343 @GuardedBy("mLock") updateSplitInfoCallbackIfNecessary()2344 private void updateSplitInfoCallbackIfNecessary() { 2345 if (!readyToReportToClient() || mSplitInfoCallback == null) { 2346 return; 2347 } 2348 final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable(); 2349 if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) { 2350 return; 2351 } 2352 mLastReportedSplitStates.clear(); 2353 mLastReportedSplitStates.addAll(currentSplitStates); 2354 mSplitInfoCallback.accept(currentSplitStates); 2355 } 2356 2357 /** 2358 * Notifies callbacks about changes to {@link ActivityStack} states if necessary. 2359 */ 2360 @GuardedBy("mLock") updateActivityStackCallbackIfNecessary()2361 private void updateActivityStackCallbackIfNecessary() { 2362 if (!readyToReportToClient() || mActivityStackCallbacks.isEmpty()) { 2363 return; 2364 } 2365 final List<ActivityStack> currentActivityStacks = getActivityStacksIfStable(); 2366 if (currentActivityStacks == null 2367 || mLastReportedActivityStacks.equals(currentActivityStacks)) { 2368 return; 2369 } 2370 mLastReportedActivityStacks.clear(); 2371 mLastReportedActivityStacks.addAll(currentActivityStacks); 2372 // Copy the map in case a callback is removed during the for-loop. 2373 final ArrayMap<Consumer<List<ActivityStack>>, Executor> callbacks = 2374 new ArrayMap<>(mActivityStackCallbacks); 2375 for (int i = callbacks.size() - 1; i >= 0; --i) { 2376 final Executor executor = callbacks.valueAt(i); 2377 final Consumer<List<ActivityStack>> callback = callbacks.keyAt(i); 2378 executor.execute(() -> callback.accept(currentActivityStacks)); 2379 } 2380 } 2381 2382 /** 2383 * Returns a list of descriptors for currently active split states. 2384 * 2385 * @return a list of descriptors for currently active split states if all the containers are in 2386 * a stable state, or {@code null} otherwise. 2387 */ 2388 @GuardedBy("mLock") 2389 @Nullable getActiveSplitStatesIfStable()2390 private List<SplitInfo> getActiveSplitStatesIfStable() { 2391 final List<SplitInfo> splitStates = new ArrayList<>(); 2392 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2393 final List<SplitInfo> taskSplitStates = 2394 mTaskContainers.valueAt(i).getSplitStatesIfStable(); 2395 if (taskSplitStates == null) { 2396 return null; 2397 } 2398 splitStates.addAll(taskSplitStates); 2399 } 2400 return splitStates; 2401 } 2402 2403 /** 2404 * Returns a list of currently active {@link ActivityStack activityStacks}. 2405 * 2406 * @return a list of {@link ActivityStack activityStacks} if all the containers are in 2407 * a stable state, or {@code null} otherwise. 2408 */ 2409 @GuardedBy("mLock") 2410 @Nullable getActivityStacksIfStable()2411 private List<ActivityStack> getActivityStacksIfStable() { 2412 final List<ActivityStack> activityStacks = new ArrayList<>(); 2413 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2414 final List<ActivityStack> taskActivityStacks = 2415 mTaskContainers.valueAt(i).getActivityStacksIfStable(); 2416 if (taskActivityStacks == null) { 2417 return null; 2418 } 2419 activityStacks.addAll(taskActivityStacks); 2420 } 2421 return activityStacks; 2422 } 2423 2424 /** 2425 * Whether we can now report the split states to the client. 2426 */ 2427 @GuardedBy("mLock") readyToReportToClient()2428 private boolean readyToReportToClient() { 2429 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2430 if (mTaskContainers.valueAt(i).isInIntermediateState()) { 2431 // If any Task is in an intermediate state, wait for the server update. 2432 return false; 2433 } 2434 } 2435 return true; 2436 } 2437 2438 /** 2439 * Returns {@code true} if the container is expanded to occupy full task size. 2440 * Returns {@code false} if the container is included in an active split. 2441 */ shouldContainerBeExpanded(@ullable TaskFragmentContainer container)2442 boolean shouldContainerBeExpanded(@Nullable TaskFragmentContainer container) { 2443 if (container == null) { 2444 return false; 2445 } 2446 return getActiveSplitForContainer(container) == null; 2447 } 2448 2449 /** 2450 * Returns a split rule for the provided pair of primary activity and secondary activity intent 2451 * if available. 2452 */ 2453 @GuardedBy("mLock") 2454 @Nullable getSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent)2455 private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, 2456 @NonNull Intent secondaryActivityIntent) { 2457 for (EmbeddingRule rule : mSplitRules) { 2458 if (!(rule instanceof SplitPairRule)) { 2459 continue; 2460 } 2461 SplitPairRule pairRule = (SplitPairRule) rule; 2462 if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) { 2463 return pairRule; 2464 } 2465 } 2466 return null; 2467 } 2468 2469 /** 2470 * Returns a split rule for the provided pair of primary and secondary activities if available. 2471 */ 2472 @GuardedBy("mLock") 2473 @Nullable getSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)2474 private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, 2475 @NonNull Activity secondaryActivity) { 2476 for (EmbeddingRule rule : mSplitRules) { 2477 if (!(rule instanceof SplitPairRule)) { 2478 continue; 2479 } 2480 SplitPairRule pairRule = (SplitPairRule) rule; 2481 final Intent intent = secondaryActivity.getIntent(); 2482 if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity) 2483 && (intent == null 2484 || pairRule.matchesActivityIntentPair(primaryActivity, intent))) { 2485 return pairRule; 2486 } 2487 } 2488 return null; 2489 } 2490 2491 @Nullable 2492 @GuardedBy("mLock") getContainer(@onNull IBinder fragmentToken)2493 TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { 2494 return getContainer(container -> fragmentToken.equals(container.getTaskFragmentToken())); 2495 } 2496 2497 @Nullable 2498 @GuardedBy("mLock") getContainer(@onNull Predicate<TaskFragmentContainer> predicate)2499 TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) { 2500 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2501 final TaskFragmentContainer container = mTaskContainers.valueAt(i) 2502 .getContainer(predicate); 2503 if (container != null) { 2504 return container; 2505 } 2506 } 2507 return null; 2508 } 2509 2510 @VisibleForTesting 2511 @Nullable 2512 @GuardedBy("mLock") getSplitContainer(@onNull IBinder token)2513 SplitContainer getSplitContainer(@NonNull IBinder token) { 2514 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2515 final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers(); 2516 for (SplitContainer container : containers) { 2517 if (container.getToken().equals(token)) { 2518 return container; 2519 } 2520 } 2521 } 2522 return null; 2523 } 2524 2525 @Nullable 2526 @GuardedBy("mLock") getTaskContainer(int taskId)2527 TaskContainer getTaskContainer(int taskId) { 2528 return mTaskContainers.get(taskId); 2529 } 2530 2531 @GuardedBy("mLock") addTaskContainer(int taskId, TaskContainer taskContainer)2532 void addTaskContainer(int taskId, TaskContainer taskContainer) { 2533 mTaskContainers.put(taskId, taskContainer); 2534 mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor)); 2535 } 2536 getHandler()2537 Handler getHandler() { 2538 return mHandler; 2539 } 2540 2541 @GuardedBy("mLock") getTaskId(@onNull Activity activity)2542 int getTaskId(@NonNull Activity activity) { 2543 // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an 2544 // IPC call. 2545 final TaskFragmentContainer container = getContainerWithActivity(activity); 2546 return container != null ? container.getTaskId() : activity.getTaskId(); 2547 } 2548 2549 @Nullable getActivity(@onNull IBinder activityToken)2550 Activity getActivity(@NonNull IBinder activityToken) { 2551 return ActivityThread.currentActivityThread().getActivity(activityToken); 2552 } 2553 2554 @VisibleForTesting 2555 @Nullable getActivityClientRecord(@onNull Activity activity)2556 ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) { 2557 return ActivityThread.currentActivityThread() 2558 .getActivityClient(activity.getActivityToken()); 2559 } 2560 2561 @VisibleForTesting getActivityStartMonitor()2562 ActivityStartMonitor getActivityStartMonitor() { 2563 return mActivityStartMonitor; 2564 } 2565 2566 /** 2567 * Gets the token of the TaskFragment that embedded this activity. It is available as soon as 2568 * the activity is created and attached, so it can be used during {@link #onActivityCreated} 2569 * before the server notifies the organizer to avoid racing condition. 2570 */ 2571 @VisibleForTesting 2572 @Nullable getTaskFragmentTokenFromActivityClientRecord(@onNull Activity activity)2573 IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) { 2574 final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); 2575 return record != null ? record.mTaskFragmentToken : null; 2576 } 2577 2578 /** 2579 * Returns {@code true} if an Activity with the provided component name should always be 2580 * expanded to occupy full task bounds. Such activity must not be put in a split. 2581 */ 2582 @VisibleForTesting 2583 @GuardedBy("mLock") shouldExpand(@ullable Activity activity, @Nullable Intent intent)2584 boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { 2585 for (EmbeddingRule rule : mSplitRules) { 2586 if (!(rule instanceof ActivityRule)) { 2587 continue; 2588 } 2589 ActivityRule activityRule = (ActivityRule) rule; 2590 if (!activityRule.shouldAlwaysExpand()) { 2591 continue; 2592 } 2593 if (activity != null && activityRule.matchesActivity(activity)) { 2594 return true; 2595 } else if (intent != null && activityRule.matchesIntent(intent)) { 2596 return true; 2597 } 2598 } 2599 return false; 2600 } 2601 2602 /** 2603 * Checks whether the associated container should be destroyed together with a finishing 2604 * container. There is a case when primary containers for placeholders should be retained 2605 * despite the rule configuration to finish primary with secondary - if they are marked as 2606 * 'sticky' and the placeholder was finished when fully overlapping the primary container. 2607 * 2608 * @return {@code true} if the associated container should be retained (and not be finished). 2609 */ 2610 // Suppress GuardedBy warning because lint ask to mark this method as 2611 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 2612 @SuppressWarnings("GuardedBy") 2613 @GuardedBy("mLock") shouldRetainAssociatedContainer(@onNull TaskFragmentContainer finishingContainer, @NonNull TaskFragmentContainer associatedContainer)2614 boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer, 2615 @NonNull TaskFragmentContainer associatedContainer) { 2616 SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, 2617 finishingContainer); 2618 if (splitContainer == null) { 2619 // Containers are not in the same split, no need to retain. 2620 return false; 2621 } 2622 // Find the finish behavior for the associated container 2623 int finishBehavior; 2624 SplitRule splitRule = splitContainer.getSplitRule(); 2625 if (finishingContainer == splitContainer.getPrimaryContainer()) { 2626 finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule); 2627 } else { 2628 finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule); 2629 } 2630 // Decide whether the associated container should be retained based on the current 2631 // presentation mode. 2632 if (shouldShowSplit(splitContainer)) { 2633 return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); 2634 } else { 2635 return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); 2636 } 2637 } 2638 2639 /** 2640 * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer) 2641 */ 2642 @GuardedBy("mLock") shouldRetainAssociatedActivity(@onNull TaskFragmentContainer finishingContainer, @NonNull Activity associatedActivity)2643 boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer, 2644 @NonNull Activity associatedActivity) { 2645 final TaskFragmentContainer associatedContainer = getContainerWithActivity( 2646 associatedActivity); 2647 if (associatedContainer == null) { 2648 return false; 2649 } 2650 2651 return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); 2652 } 2653 2654 /** 2655 * Gets all overlay containers from all tasks in this process, or an empty list if there's 2656 * no overlay container. 2657 */ 2658 @VisibleForTesting 2659 @GuardedBy("mLock") 2660 @NonNull getAllNonFinishingOverlayContainers()2661 List<TaskFragmentContainer> getAllNonFinishingOverlayContainers() { 2662 final List<TaskFragmentContainer> overlayContainers = new ArrayList<>(); 2663 for (int i = 0; i < mTaskContainers.size(); i++) { 2664 final TaskContainer taskContainer = mTaskContainers.valueAt(i); 2665 final List<TaskFragmentContainer> overlayContainersPerTask = taskContainer 2666 .getTaskFragmentContainers() 2667 .stream() 2668 .filter(c -> c.isOverlay() && !c.isFinished()) 2669 .toList(); 2670 overlayContainers.addAll(overlayContainersPerTask); 2671 } 2672 return overlayContainers; 2673 } 2674 2675 @GuardedBy("mLock") isAssociatedWithOverlay(@onNull Activity activity)2676 private boolean isAssociatedWithOverlay(@NonNull Activity activity) { 2677 final TaskContainer taskContainer = getTaskContainer(getTaskId(activity)); 2678 if (taskContainer == null) { 2679 return false; 2680 } 2681 return taskContainer.getContainer(c -> c.isOverlay() && !c.isFinished() 2682 && c.getAssociatedActivityToken() == activity.getActivityToken()) != null; 2683 } 2684 2685 /** 2686 * Creates an overlay container or updates a visible overlay container if its 2687 * {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()} 2688 * and {@link TaskFragmentContainer#getAssociatedActivityToken()} matches. 2689 * <p> 2690 * This method will also dismiss any existing overlay container if: 2691 * <ul> 2692 * <li>it's visible but not meet the criteria to update overlay</li> 2693 * <li>{@link TaskFragmentContainer#getOverlayTag()} matches but not meet the criteria to 2694 * update overlay</li> 2695 * </ul> 2696 * 2697 * @param wct the {@link WindowContainerTransaction} 2698 * @param options the {@link ActivityOptions} to launch the overlay 2699 * @param intent the intent of activity to launch 2700 * @param launchActivity the activity to launch the overlay container 2701 * @return the overlay container 2702 */ 2703 @VisibleForTesting 2704 // Suppress GuardedBy warning because lint ask to mark this method as 2705 // @GuardedBy(container.mController.mLock), which is mLock itself 2706 @SuppressWarnings("GuardedBy") 2707 @GuardedBy("mLock") 2708 @Nullable createOrUpdateOverlayTaskFragmentIfNeeded( @onNull WindowContainerTransaction wct, @NonNull Bundle options, @NonNull Intent intent, @NonNull Activity launchActivity)2709 TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded( 2710 @NonNull WindowContainerTransaction wct, @NonNull Bundle options, 2711 @NonNull Intent intent, @NonNull Activity launchActivity) { 2712 if (isActivityFromSplit(launchActivity)) { 2713 // We restrict to launch the overlay from split. Fallback to treat it as normal 2714 // launch. 2715 return null; 2716 } 2717 2718 final List<TaskFragmentContainer> overlayContainers = 2719 getAllNonFinishingOverlayContainers(); 2720 final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG)); 2721 final boolean associateLaunchingActivity = options 2722 .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true); 2723 2724 // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions 2725 // specified by Intent, expand the overlay container to fill the parent task instead. 2726 final ActivityStackAttributesCalculatorParams params = 2727 new ActivityStackAttributesCalculatorParams( 2728 mPresenter.createParentContainerInfoFromTaskProperties( 2729 mPresenter.getTaskProperties(launchActivity)), overlayTag, options); 2730 // Fallback to expand the bounds if there's no activityStackAttributes calculator. 2731 final ActivityStackAttributes attrs; 2732 if (mActivityStackAttributesCalculator != null) { 2733 attrs = mActivityStackAttributesCalculator.apply(params); 2734 } else { 2735 attrs = new ActivityStackAttributes.Builder().build(); 2736 Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay " 2737 + "container as expected."); 2738 } 2739 2740 final int taskId = getTaskId(launchActivity); 2741 if (!overlayContainers.isEmpty()) { 2742 for (final TaskFragmentContainer overlayContainer : overlayContainers) { 2743 final boolean isTopNonFinishingOverlay = overlayContainer.equals( 2744 overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer( 2745 true /* includePin */, true /* includeOverlay */)); 2746 if (taskId != overlayContainer.getTaskId()) { 2747 // If there's an overlay container with same tag in a different task, 2748 // dismiss the overlay container since the tag must be unique per process. 2749 if (overlayTag.equals(overlayContainer.getOverlayTag())) { 2750 Log.w(TAG, "The overlay container with tag:" 2751 + overlayContainer.getOverlayTag() + " is dismissed because" 2752 + " there's an existing overlay container with the same tag but" 2753 + " different task ID:" + overlayContainer.getTaskId() + ". " 2754 + "The new associated activity is " + launchActivity); 2755 mPresenter.cleanupContainer(wct, overlayContainer, 2756 false /* shouldFinishDependant */); 2757 } 2758 continue; 2759 } 2760 if (!overlayTag.equals(overlayContainer.getOverlayTag())) { 2761 // If there's an overlay container with different tag on top in the same 2762 // task, dismiss the existing overlay container. 2763 if (isTopNonFinishingOverlay) { 2764 mPresenter.cleanupContainer(wct, overlayContainer, 2765 false /* shouldFinishDependant */); 2766 } 2767 continue; 2768 } 2769 // The overlay container has the same tag and task ID with the new launching 2770 // overlay container. 2771 if (!isTopNonFinishingOverlay) { 2772 // Dismiss the invisible overlay container regardless of activity 2773 // association if it collides the tag of new launched overlay container . 2774 Log.w(TAG, "The invisible overlay container with tag:" 2775 + overlayContainer.getOverlayTag() + " is dismissed because" 2776 + " there's a launching overlay container with the same tag." 2777 + " The new associated activity is " + launchActivity); 2778 mPresenter.cleanupContainer(wct, overlayContainer, 2779 false /* shouldFinishDependant */); 2780 continue; 2781 } 2782 // Requesting an always-on-top overlay. 2783 if (!associateLaunchingActivity) { 2784 if (overlayContainer.isOverlayWithActivityAssociation()) { 2785 // Dismiss the overlay container since it has associated with an activity. 2786 Log.w(TAG, "The overlay container with tag:" 2787 + overlayContainer.getOverlayTag() + " is dismissed because" 2788 + " there's an existing overlay container with the same tag but" 2789 + " different associated launching activity. The overlay container" 2790 + " doesn't associate with any activity."); 2791 mPresenter.cleanupContainer(wct, overlayContainer, 2792 false /* shouldFinishDependant */); 2793 continue; 2794 } else { 2795 // The existing overlay container doesn't associate an activity as well. 2796 // Just update the overlay and return. 2797 // Note that going to this condition means the tag, task ID matches a 2798 // visible always-on-top overlay, and won't dismiss any overlay any more. 2799 mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs, 2800 getMinDimensions(intent)); 2801 return overlayContainer; 2802 } 2803 } 2804 if (launchActivity.getActivityToken() 2805 != overlayContainer.getAssociatedActivityToken()) { 2806 Log.w(TAG, "The overlay container with tag:" 2807 + overlayContainer.getOverlayTag() + " is dismissed because" 2808 + " there's an existing overlay container with the same tag but" 2809 + " different associated launching activity. The new associated" 2810 + " activity is " + launchActivity); 2811 // The associated activity must be the same, or it will be dismissed. 2812 mPresenter.cleanupContainer(wct, overlayContainer, 2813 false /* shouldFinishDependant */); 2814 continue; 2815 } 2816 // Reaching here means the launching activity launch an overlay container with the 2817 // same task ID, tag, while there's a previously launching visible overlay 2818 // container. We'll regard it as updating the existing overlay container. 2819 mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs, 2820 getMinDimensions(intent)); 2821 return overlayContainer; 2822 2823 } 2824 } 2825 // Launch the overlay container to the task with taskId. 2826 return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag, 2827 options, associateLaunchingActivity); 2828 } 2829 2830 @GuardedBy("mLock") isActivityFromSplit(@onNull Activity activity)2831 private boolean isActivityFromSplit(@NonNull Activity activity) { 2832 final TaskFragmentContainer container = getContainerWithActivity(activity); 2833 if (container == null) { 2834 return false; 2835 } 2836 return getActiveSplitForContainer(container) != null; 2837 } 2838 2839 private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter { 2840 2841 @Override onActivityPreCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)2842 public void onActivityPreCreated(@NonNull Activity activity, 2843 @Nullable Bundle savedInstanceState) { 2844 if (activity.isChild()) { 2845 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2846 // window will just be a child of the parent Activity window. 2847 return; 2848 } 2849 synchronized (mLock) { 2850 final IBinder activityToken = activity.getActivityToken(); 2851 final IBinder initialTaskFragmentToken = 2852 getTaskFragmentTokenFromActivityClientRecord(activity); 2853 // If the activity is not embedded, then it will not have an initial task fragment 2854 // token so no further action is needed. 2855 if (initialTaskFragmentToken == null) { 2856 return; 2857 } 2858 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2859 final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) 2860 .getTaskFragmentContainers(); 2861 for (int j = containers.size() - 1; j >= 0; j--) { 2862 final TaskFragmentContainer container = containers.get(j); 2863 if (!container.hasActivity(activityToken) 2864 && container.getTaskFragmentToken() 2865 .equals(initialTaskFragmentToken)) { 2866 if (ActivityClient.getInstance().isRequestedToLaunchInTaskFragment( 2867 activityToken, initialTaskFragmentToken)) { 2868 container.addPendingAppearedInRequestedTaskFragmentActivity( 2869 activity); 2870 } 2871 2872 // The onTaskFragmentInfoChanged callback containing this activity has 2873 // not reached the client yet, so add the activity to the pending 2874 // appeared activities. 2875 container.addPendingAppearedActivity(activity); 2876 return; 2877 } 2878 } 2879 } 2880 } 2881 } 2882 2883 @Override onActivityPostCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)2884 public void onActivityPostCreated(@NonNull Activity activity, 2885 @Nullable Bundle savedInstanceState) { 2886 if (activity.isChild()) { 2887 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2888 // window will just be a child of the parent Activity window. 2889 return; 2890 } 2891 // Calling after Activity#onCreate is complete to allow the app launch something 2892 // first. In case of a configured placeholder activity we want to make sure 2893 // that we don't launch it if an activity itself already requested something to be 2894 // launched to side. 2895 synchronized (mLock) { 2896 final TransactionRecord transactionRecord = mTransactionManager 2897 .startNewTransaction(); 2898 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); 2899 SplitController.this.onActivityCreated(transactionRecord.getTransaction(), 2900 activity); 2901 // The WCT should be applied and merged to the activity launch transition. 2902 transactionRecord.apply(false /* shouldApplyIndependently */); 2903 } 2904 } 2905 2906 @Override onActivityConfigurationChanged(@onNull Activity activity)2907 public void onActivityConfigurationChanged(@NonNull Activity activity) { 2908 if (activity.isChild()) { 2909 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2910 // window will just be a child of the parent Activity window. 2911 return; 2912 } 2913 synchronized (mLock) { 2914 final TransactionRecord transactionRecord = mTransactionManager 2915 .startNewTransaction(); 2916 SplitController.this.onActivityConfigurationChanged( 2917 transactionRecord.getTransaction(), activity); 2918 // The WCT should be applied and merged to the Task change transition so that the 2919 // placeholder is launched in the same transition. 2920 transactionRecord.apply(false /* shouldApplyIndependently */); 2921 } 2922 } 2923 2924 @Override onActivityPostPaused(@onNull Activity activity)2925 public void onActivityPostPaused(@NonNull Activity activity) { 2926 if (activity.isChild()) { 2927 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2928 // window will just be a child of the parent Activity window. 2929 return; 2930 } 2931 synchronized (mLock) { 2932 final TransactionRecord transactionRecord = mTransactionManager 2933 .startNewTransaction(); 2934 transactionRecord.setOriginType(TRANSIT_CLOSE); 2935 SplitController.this.onActivityPaused( 2936 transactionRecord.getTransaction(), activity); 2937 transactionRecord.apply(false /* shouldApplyIndependently */); 2938 } 2939 } 2940 2941 @Override onActivityPostDestroyed(@onNull Activity activity)2942 public void onActivityPostDestroyed(@NonNull Activity activity) { 2943 if (activity.isChild()) { 2944 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2945 // window will just be a child of the parent Activity window. 2946 return; 2947 } 2948 synchronized (mLock) { 2949 final TransactionRecord transactionRecord = mTransactionManager 2950 .startNewTransaction(); 2951 transactionRecord.setOriginType(TRANSIT_CLOSE); 2952 SplitController.this.onActivityDestroyed( 2953 transactionRecord.getTransaction(), activity); 2954 transactionRecord.apply(false /* shouldApplyIndependently */); 2955 } 2956 } 2957 } 2958 2959 /** 2960 * Executor that posts on the main application thread. 2961 */ 2962 private static class MainThreadExecutor implements Executor { 2963 private final Handler mHandler = new Handler(Looper.getMainLooper()); 2964 2965 @Override execute(@onNull Runnable r)2966 public void execute(@NonNull Runnable r) { 2967 mHandler.post(r); 2968 } 2969 } 2970 2971 /** 2972 * A monitor that intercepts all activity start requests originating in the client process and 2973 * can amend them to target a specific task fragment to form a split. 2974 */ 2975 @VisibleForTesting 2976 class ActivityStartMonitor extends Instrumentation.ActivityMonitor { 2977 @VisibleForTesting 2978 @GuardedBy("mLock") 2979 Intent mCurrentIntent; 2980 2981 @Override onStartActivity(@onNull Context who, @NonNull Intent intent, @NonNull Bundle options)2982 public Instrumentation.ActivityResult onStartActivity(@NonNull Context who, 2983 @NonNull Intent intent, @NonNull Bundle options) { 2984 // TODO(b/232042367): Consolidate the activity create handling so that we can handle 2985 // cross-process the same as normal. 2986 2987 final Bundle bundle = options.getBundle(KEY_ACTIVITY_STACK_TOKEN); 2988 if (bundle != null) { 2989 final IBinder activityStackToken = ActivityStack.Token.readFromBundle(bundle) 2990 .getRawToken(); 2991 // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity 2992 // into the taskFragment associated with the token. 2993 options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken); 2994 } 2995 2996 // Early return if the launching taskfragment is already been set. 2997 // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to 2998 // bundle. This is still needed to support #setLaunchingActivityStack. 2999 if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { 3000 synchronized (mLock) { 3001 mCurrentIntent = intent; 3002 } 3003 return super.onStartActivity(who, intent, options); 3004 } 3005 3006 final Activity launchingActivity; 3007 if (who instanceof Activity) { 3008 // We will check if the new activity should be split with the activity that launched 3009 // it. 3010 final Activity activity = (Activity) who; 3011 // For Activity that is child of another Activity (ActivityGroup), treat the parent 3012 // Activity as the launching one because it's window will just be a child of the 3013 // parent Activity window. 3014 launchingActivity = activity.isChild() ? activity.getParent() : activity; 3015 if (isInPictureInPicture(launchingActivity)) { 3016 // We don't embed activity when it is in PIP. 3017 return super.onStartActivity(who, intent, options); 3018 } 3019 } else { 3020 // When the context to start activity is not an Activity context, we will check if 3021 // the new activity should be embedded in the known Task belonging to the organizer 3022 // process. @see #resolveStartActivityIntentFromNonActivityContext 3023 // It is a current security limitation that we can't access the activity info of 3024 // other process even if it is in the same Task. 3025 launchingActivity = null; 3026 } 3027 3028 synchronized (mLock) { 3029 final TransactionRecord transactionRecord = mTransactionManager 3030 .startNewTransaction(); 3031 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); 3032 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 3033 final TaskFragmentContainer launchedInTaskFragment; 3034 if (launchingActivity != null) { 3035 final int taskId = getTaskId(launchingActivity); 3036 final String overlayTag = options.getString(KEY_OVERLAY_TAG); 3037 if (Flags.activityEmbeddingOverlayPresentationFlag() 3038 && overlayTag != null) { 3039 launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct, 3040 options, intent, launchingActivity); 3041 } else { 3042 launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, 3043 launchingActivity); 3044 } 3045 } else { 3046 launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct, 3047 intent); 3048 } 3049 if (launchedInTaskFragment != null) { 3050 // Make sure the WCT is applied immediately instead of being queued so that the 3051 // TaskFragment will be ready before activity attachment. 3052 transactionRecord.apply(false /* shouldApplyIndependently */); 3053 // Amend the request to let the WM know that the activity should be placed in 3054 // the dedicated container. 3055 // TODO(b/229680885): skip override launching TaskFragment token by split-rule 3056 options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, 3057 launchedInTaskFragment.getTaskFragmentToken()); 3058 mCurrentIntent = intent; 3059 } else { 3060 transactionRecord.abort(); 3061 } 3062 } 3063 3064 return super.onStartActivity(who, intent, options); 3065 } 3066 3067 @Override onStartActivityResult(int result, @NonNull Bundle bOptions)3068 public void onStartActivityResult(int result, @NonNull Bundle bOptions) { 3069 super.onStartActivityResult(result, bOptions); 3070 synchronized (mLock) { 3071 if (mCurrentIntent != null && result != START_SUCCESS) { 3072 // Clear the pending appeared intent if the activity was not started 3073 // successfully. 3074 final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); 3075 if (token != null) { 3076 final TaskFragmentContainer container = getContainer(token); 3077 if (container != null) { 3078 container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); 3079 } 3080 } 3081 } 3082 mCurrentIntent = null; 3083 } 3084 } 3085 } 3086 3087 /** 3088 * Checks if an activity is embedded and its presentation is customized by a 3089 * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds. 3090 */ 3091 @Override isActivityEmbedded(@onNull Activity activity)3092 public boolean isActivityEmbedded(@NonNull Activity activity) { 3093 Objects.requireNonNull(activity); 3094 synchronized (mLock) { 3095 if (Flags.activityWindowInfoFlag()) { 3096 final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); 3097 return activityWindowInfo != null && activityWindowInfo.isEmbedded(); 3098 } 3099 return mPresenter.isActivityEmbedded(activity.getActivityToken()); 3100 } 3101 } 3102 3103 @Override setEmbeddedActivityWindowInfoCallback(@onNull Executor executor, @NonNull Consumer<EmbeddedActivityWindowInfo> callback)3104 public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor, 3105 @NonNull Consumer<EmbeddedActivityWindowInfo> callback) { 3106 if (!Flags.activityWindowInfoFlag()) { 3107 return; 3108 } 3109 Objects.requireNonNull(executor); 3110 Objects.requireNonNull(callback); 3111 synchronized (mLock) { 3112 if (mEmbeddedActivityWindowInfoCallback == null) { 3113 ClientTransactionListenerController.getInstance() 3114 .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener()); 3115 } 3116 mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback); 3117 } 3118 } 3119 3120 @Override clearEmbeddedActivityWindowInfoCallback()3121 public void clearEmbeddedActivityWindowInfoCallback() { 3122 if (!Flags.activityWindowInfoFlag()) { 3123 return; 3124 } 3125 synchronized (mLock) { 3126 if (mEmbeddedActivityWindowInfoCallback == null) { 3127 return; 3128 } 3129 mEmbeddedActivityWindowInfoCallback = null; 3130 ClientTransactionListenerController.getInstance() 3131 .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener()); 3132 } 3133 } 3134 3135 @VisibleForTesting 3136 @GuardedBy("mLock") 3137 @Nullable getActivityWindowInfoListener()3138 BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() { 3139 return mActivityWindowInfoListener; 3140 } 3141 3142 @Nullable 3143 @Override getEmbeddedActivityWindowInfo(@onNull Activity activity)3144 public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) { 3145 if (!Flags.activityWindowInfoFlag()) { 3146 return null; 3147 } 3148 synchronized (mLock) { 3149 final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); 3150 return activityWindowInfo != null 3151 ? translateActivityWindowInfo(activity, activityWindowInfo) 3152 : null; 3153 } 3154 } 3155 3156 @VisibleForTesting onActivityWindowInfoChanged(@onNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo)3157 void onActivityWindowInfoChanged(@NonNull IBinder activityToken, 3158 @NonNull ActivityWindowInfo activityWindowInfo) { 3159 synchronized (mLock) { 3160 if (mEmbeddedActivityWindowInfoCallback == null) { 3161 return; 3162 } 3163 final Executor executor = mEmbeddedActivityWindowInfoCallback.first; 3164 final Consumer<EmbeddedActivityWindowInfo> callback = 3165 mEmbeddedActivityWindowInfoCallback.second; 3166 3167 final Activity activity = getActivity(activityToken); 3168 if (activity == null) { 3169 return; 3170 } 3171 final EmbeddedActivityWindowInfo info = translateActivityWindowInfo( 3172 activity, activityWindowInfo); 3173 3174 executor.execute(() -> callback.accept(info)); 3175 } 3176 } 3177 3178 @Nullable getActivityWindowInfo(@onNull Activity activity)3179 private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { 3180 if (activity.isFinishing()) { 3181 return null; 3182 } 3183 final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); 3184 return record != null ? record.getActivityWindowInfo() : null; 3185 } 3186 3187 @NonNull translateActivityWindowInfo( @onNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo)3188 private static EmbeddedActivityWindowInfo translateActivityWindowInfo( 3189 @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) { 3190 final boolean isEmbedded = activityWindowInfo.isEmbedded(); 3191 final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds()); 3192 final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds()); 3193 return new EmbeddedActivityWindowInfo(activity, isEmbedded, taskBounds, 3194 activityStackBounds); 3195 } 3196 3197 /** 3198 * If the two rules have the same presentation, and the calculated {@link SplitAttributes} 3199 * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same 3200 * {@link SplitContainer} if there is any. 3201 */ canReuseContainer(@onNull SplitRule rule1, @NonNull SplitRule rule2, @NonNull WindowMetrics parentWindowMetrics, @NonNull SplitAttributes calculatedSplitAttributes, @NonNull SplitAttributes containerSplitAttributes)3202 private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2, 3203 @NonNull WindowMetrics parentWindowMetrics, 3204 @NonNull SplitAttributes calculatedSplitAttributes, 3205 @NonNull SplitAttributes containerSplitAttributes) { 3206 if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { 3207 return false; 3208 } 3209 return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2, 3210 parentWindowMetrics) 3211 // Besides rules, we should also check whether the SplitContainer's splitAttributes 3212 // matches the current splitAttributes or not. The splitAttributes may change 3213 // if the app chooses different SplitAttributes calculator function before a new 3214 // activity is started even they match the same splitRule. 3215 && calculatedSplitAttributes.equals(containerSplitAttributes); 3216 } 3217 3218 /** 3219 * Whether the two rules have the same presentation. 3220 */ 3221 @VisibleForTesting areRulesSamePresentation(@onNull SplitPairRule rule1, @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics)3222 static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1, 3223 @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) { 3224 if (rule1.getTag() != null || rule2.getTag() != null) { 3225 // Tag must be unique if it is set. We don't want to reuse the container if the rules 3226 // have different tags because they can have different SplitAttributes later through 3227 // SplitAttributesCalculator. 3228 return Objects.equals(rule1.getTag(), rule2.getTag()); 3229 } 3230 // If both rules don't have tag, compare all SplitRules' properties that may affect their 3231 // SplitAttributes. 3232 // TODO(b/231655482): add util method to do the comparison in SplitPairRule. 3233 return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes()) 3234 && rule1.checkParentMetrics(parentWindowMetrics) 3235 == rule2.checkParentMetrics(parentWindowMetrics) 3236 && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary() 3237 && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary(); 3238 } 3239 3240 /** 3241 * Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given 3242 * rule. 3243 */ isContainerReusableRule(@onNull SplitRule rule)3244 private static boolean isContainerReusableRule(@NonNull SplitRule rule) { 3245 // We don't expect to reuse the placeholder rule. 3246 if (!(rule instanceof SplitPairRule)) { 3247 return false; 3248 } 3249 final SplitPairRule pairRule = (SplitPairRule) rule; 3250 3251 // Not reuse if it needs to destroy the existing. 3252 return !pairRule.shouldClearTop(); 3253 } 3254 isInPictureInPicture(@onNull Activity activity)3255 private static boolean isInPictureInPicture(@NonNull Activity activity) { 3256 return isInPictureInPicture(activity.getResources().getConfiguration()); 3257 } 3258 isInPictureInPicture(@onNull TaskFragmentContainer tf)3259 private static boolean isInPictureInPicture(@NonNull TaskFragmentContainer tf) { 3260 return isInPictureInPicture(tf.getInfo().getConfiguration()); 3261 } 3262 isInPictureInPicture(@ullable Configuration configuration)3263 private static boolean isInPictureInPicture(@Nullable Configuration configuration) { 3264 return configuration != null 3265 && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; 3266 } 3267 3268 @GuardedBy("mLock") updateDivider( @onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)3269 void updateDivider( 3270 @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { 3271 final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId()); 3272 final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo(); 3273 dividerPresenter.updateDivider( 3274 wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); 3275 } 3276 3277 @Override onStartDragging(@onNull Consumer<WindowContainerTransaction> action)3278 public void onStartDragging(@NonNull Consumer<WindowContainerTransaction> action) { 3279 synchronized (mLock) { 3280 final TransactionRecord transactionRecord = 3281 mTransactionManager.startNewTransaction(); 3282 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 3283 action.accept(wct); 3284 transactionRecord.apply(false /* shouldApplyIndependently */); 3285 } 3286 } 3287 3288 @Override onFinishDragging( int taskId, @NonNull Consumer<WindowContainerTransaction> action)3289 public void onFinishDragging( 3290 int taskId, 3291 @NonNull Consumer<WindowContainerTransaction> action) { 3292 synchronized (mLock) { 3293 final TransactionRecord transactionRecord = 3294 mTransactionManager.startNewTransaction(); 3295 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_DRAG_RESIZE); 3296 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 3297 final TaskContainer taskContainer = mTaskContainers.get(taskId); 3298 if (taskContainer != null) { 3299 final DividerPresenter dividerPresenter = 3300 mDividerPresenters.get(taskContainer.getTaskId()); 3301 final List<TaskFragmentContainer> containersToFinish = new ArrayList<>(); 3302 taskContainer.updateTopSplitContainerForDivider( 3303 dividerPresenter, containersToFinish); 3304 for (final TaskFragmentContainer container : containersToFinish) { 3305 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 3306 } 3307 updateContainersInTask(wct, taskContainer); 3308 } 3309 action.accept(wct); 3310 transactionRecord.apply(false /* shouldApplyIndependently */); 3311 } 3312 } 3313 } 3314