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