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.WindowConfiguration.WINDOWING_MODE_PINNED; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; 24 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; 25 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; 26 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; 27 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; 28 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE; 29 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; 30 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; 31 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; 32 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; 33 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; 34 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; 35 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; 36 37 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; 38 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; 39 import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; 40 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; 41 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; 42 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; 43 import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair; 44 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; 45 import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics; 46 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; 47 48 import android.app.Activity; 49 import android.app.ActivityClient; 50 import android.app.ActivityOptions; 51 import android.app.ActivityThread; 52 import android.app.Application; 53 import android.app.Instrumentation; 54 import android.content.ComponentName; 55 import android.content.Context; 56 import android.content.Intent; 57 import android.content.res.Configuration; 58 import android.graphics.Rect; 59 import android.os.Bundle; 60 import android.os.Handler; 61 import android.os.IBinder; 62 import android.os.Looper; 63 import android.os.SystemProperties; 64 import android.util.ArraySet; 65 import android.util.Log; 66 import android.util.Pair; 67 import android.util.Size; 68 import android.util.SparseArray; 69 import android.view.WindowMetrics; 70 import android.window.TaskFragmentAnimationParams; 71 import android.window.TaskFragmentInfo; 72 import android.window.TaskFragmentOperation; 73 import android.window.TaskFragmentParentInfo; 74 import android.window.TaskFragmentTransaction; 75 import android.window.WindowContainerTransaction; 76 77 import androidx.annotation.GuardedBy; 78 import androidx.annotation.NonNull; 79 import androidx.annotation.Nullable; 80 import androidx.window.common.CommonFoldingFeature; 81 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; 82 import androidx.window.common.EmptyLifecycleCallbacksAdapter; 83 import androidx.window.extensions.WindowExtensionsImpl; 84 import androidx.window.extensions.core.util.function.Consumer; 85 import androidx.window.extensions.core.util.function.Function; 86 import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; 87 import androidx.window.extensions.layout.WindowLayoutComponentImpl; 88 89 import com.android.internal.annotations.VisibleForTesting; 90 91 import java.util.ArrayList; 92 import java.util.Collections; 93 import java.util.List; 94 import java.util.Objects; 95 import java.util.Set; 96 import java.util.concurrent.Executor; 97 98 /** 99 * Main controller class that manages split states and presentation. 100 */ 101 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, 102 ActivityEmbeddingComponent { 103 static final String TAG = "SplitController"; 104 static final boolean ENABLE_SHELL_TRANSITIONS = 105 SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); 106 107 @VisibleForTesting 108 @GuardedBy("mLock") 109 final SplitPresenter mPresenter; 110 111 @VisibleForTesting 112 @GuardedBy("mLock") 113 final TransactionManager mTransactionManager; 114 115 // Currently applied split configuration. 116 @GuardedBy("mLock") 117 private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); 118 119 /** 120 * A developer-defined {@link SplitAttributes} calculator to compute the current 121 * {@link SplitAttributes} with the current device and window states. 122 * It is registered via {@link #setSplitAttributesCalculator(Function)} 123 * and unregistered via {@link #clearSplitAttributesCalculator()}. 124 * This is called when: 125 * <ul> 126 * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer, 127 * WindowContainerTransaction)}</li> 128 * <li>There's a started Activity which matches {@link SplitPairRule} </li> 129 * <li>Checking whether the place holder should be launched if there's a Activity matches 130 * {@link SplitPlaceholderRule} </li> 131 * </ul> 132 */ 133 @GuardedBy("mLock") 134 @Nullable 135 private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator; 136 137 /** 138 * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info 139 * below it. 140 * When the app is host of multiple Tasks, there can be multiple splits controlled by the same 141 * organizer. 142 */ 143 @VisibleForTesting 144 @GuardedBy("mLock") 145 final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); 146 147 /** Callback to Jetpack to notify about changes to split states. */ 148 @GuardedBy("mLock") 149 @Nullable 150 private Consumer<List<SplitInfo>> mEmbeddingCallback; 151 private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); 152 private final Handler mHandler; 153 final Object mLock = new Object(); 154 private final ActivityStartMonitor mActivityStartMonitor; 155 SplitController(@onNull WindowLayoutComponentImpl windowLayoutComponent, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer)156 public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent, 157 @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { 158 final MainThreadExecutor executor = new MainThreadExecutor(); 159 mHandler = executor.mHandler; 160 mPresenter = new SplitPresenter(executor, windowLayoutComponent, this); 161 mTransactionManager = new TransactionManager(mPresenter); 162 final ActivityThread activityThread = ActivityThread.currentActivityThread(); 163 final Application application = activityThread.getApplication(); 164 // Register a callback to be notified about activities being created. 165 application.registerActivityLifecycleCallbacks(new LifecycleCallbacks()); 166 // Intercept activity starts to route activities to new containers if necessary. 167 Instrumentation instrumentation = activityThread.getInstrumentation(); 168 169 mActivityStartMonitor = new ActivityStartMonitor(); 170 instrumentation.addMonitor(mActivityStartMonitor); 171 foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener()); 172 } 173 174 private class FoldingFeatureListener 175 implements java.util.function.Consumer<List<CommonFoldingFeature>> { 176 @Override accept(List<CommonFoldingFeature> foldingFeatures)177 public void accept(List<CommonFoldingFeature> foldingFeatures) { 178 synchronized (mLock) { 179 final TransactionRecord transactionRecord = mTransactionManager 180 .startNewTransaction(); 181 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 182 for (int i = 0; i < mTaskContainers.size(); i++) { 183 final TaskContainer taskContainer = mTaskContainers.valueAt(i); 184 if (!taskContainer.isVisible()) { 185 continue; 186 } 187 if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) { 188 continue; 189 } 190 // TODO(b/238948678): Support reporting display features in all windowing modes. 191 if (taskContainer.isInMultiWindow()) { 192 continue; 193 } 194 if (taskContainer.isEmpty()) { 195 continue; 196 } 197 updateContainersInTask(wct, taskContainer); 198 } 199 // The WCT should be applied and merged to the device state change transition if 200 // there is one. 201 transactionRecord.apply(false /* shouldApplyIndependently */); 202 } 203 } 204 } 205 206 /** Updates the embedding rules applied to future activity launches. */ 207 @Override setEmbeddingRules(@onNull Set<EmbeddingRule> rules)208 public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) { 209 synchronized (mLock) { 210 mSplitRules.clear(); 211 mSplitRules.addAll(rules); 212 } 213 } 214 215 @Override pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule)216 public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) { 217 synchronized (mLock) { 218 final TaskContainer task = getTaskContainer(taskId); 219 if (task == null) { 220 Log.e(TAG, "Cannot find the task for id: " + taskId); 221 return false; 222 } 223 224 final TaskFragmentContainer topContainer = 225 task.getTopNonFinishingTaskFragmentContainer(); 226 // Cannot pin the TaskFragment if no other TaskFragment behind it. 227 if (topContainer == null || task.indexOf(topContainer) <= 0) { 228 Log.w(TAG, "Cannot find an ActivityStack to pin or split"); 229 return false; 230 } 231 // Abort if the top container is already pinned. 232 if (task.getSplitPinContainer() != null) { 233 Log.w(TAG, "There is already a pinned ActivityStack."); 234 return false; 235 } 236 237 // Find a valid adjacent TaskFragmentContainer 238 final TaskFragmentContainer primaryContainer = 239 task.getNonFinishingTaskFragmentContainerBelow(topContainer); 240 if (primaryContainer == null) { 241 Log.w(TAG, "Cannot find another ActivityStack to split"); 242 return false; 243 } 244 245 // Abort if no space to split. 246 final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( 247 task.getTaskProperties(), splitPinRule, 248 splitPinRule.getDefaultSplitAttributes(), 249 getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(), 250 topContainer.getTopNonFinishingActivity())); 251 if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) { 252 Log.w(TAG, "No space to split, abort pinning top ActivityStack."); 253 return false; 254 } 255 256 // Registers a Split 257 final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer, 258 topContainer, splitPinRule, calculatedSplitAttributes); 259 task.addSplitContainer(splitPinContainer); 260 261 // Updates the Split 262 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 263 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 264 mPresenter.updateSplitContainer(splitPinContainer, wct); 265 transactionRecord.apply(false /* shouldApplyIndependently */); 266 updateCallbackIfNecessary(); 267 return true; 268 } 269 } 270 271 @Override unpinTopActivityStack(int taskId)272 public void unpinTopActivityStack(int taskId){ 273 synchronized (mLock) { 274 final TaskContainer task = getTaskContainer(taskId); 275 if (task == null) { 276 Log.e(TAG, "Cannot find the task to unpin, id: " + taskId); 277 return; 278 } 279 280 final SplitPinContainer splitPinContainer = task.getSplitPinContainer(); 281 if (splitPinContainer == null) { 282 Log.e(TAG, "No ActivityStack is pinned."); 283 return; 284 } 285 286 // Remove the SplitPinContainer from the task. 287 final TaskFragmentContainer containerToUnpin = 288 splitPinContainer.getSecondaryContainer(); 289 task.removeSplitPinContainer(); 290 291 // Resets the isolated navigation and updates the container. 292 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 293 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 294 mPresenter.setTaskFragmentIsolatedNavigation(wct, 295 containerToUnpin.getTaskFragmentToken(), false /* isolated */); 296 updateContainer(wct, containerToUnpin); 297 transactionRecord.apply(false /* shouldApplyIndependently */); 298 updateCallbackIfNecessary(); 299 } 300 } 301 302 @Override setSplitAttributesCalculator( @onNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator)303 public void setSplitAttributesCalculator( 304 @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) { 305 synchronized (mLock) { 306 mSplitAttributesCalculator = calculator; 307 } 308 } 309 310 @Override clearSplitAttributesCalculator()311 public void clearSplitAttributesCalculator() { 312 synchronized (mLock) { 313 mSplitAttributesCalculator = null; 314 } 315 } 316 317 @GuardedBy("mLock") 318 @Nullable getSplitAttributesCalculator()319 Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() { 320 return mSplitAttributesCalculator; 321 } 322 323 @Override 324 @NonNull setLaunchingActivityStack(@onNull ActivityOptions options, @NonNull IBinder token)325 public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options, 326 @NonNull IBinder token) { 327 options.setLaunchTaskFragmentToken(token); 328 return options; 329 } 330 331 @NonNull 332 @GuardedBy("mLock") 333 @VisibleForTesting getSplitRules()334 List<EmbeddingRule> getSplitRules() { 335 return mSplitRules; 336 } 337 338 /** 339 * Registers the split organizer callback to notify about changes to active splits. 340 * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with 341 * {@link WindowExtensionsImpl#getVendorApiLevel()} 2. 342 */ 343 @Deprecated 344 @Override setSplitInfoCallback( @onNull java.util.function.Consumer<List<SplitInfo>> callback)345 public void setSplitInfoCallback( 346 @NonNull java.util.function.Consumer<List<SplitInfo>> callback) { 347 Consumer<List<SplitInfo>> oemConsumer = callback::accept; 348 setSplitInfoCallback(oemConsumer); 349 } 350 351 /** 352 * Registers the split organizer callback to notify about changes to active splits. 353 * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2 354 */ setSplitInfoCallback(Consumer<List<SplitInfo>> callback)355 public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) { 356 synchronized (mLock) { 357 mEmbeddingCallback = callback; 358 updateCallbackIfNecessary(); 359 } 360 } 361 362 /** 363 * Clears the listener set in {@link SplitController#setSplitInfoCallback(Consumer)}. 364 */ 365 @Override clearSplitInfoCallback()366 public void clearSplitInfoCallback() { 367 synchronized (mLock) { 368 mEmbeddingCallback = null; 369 } 370 } 371 372 @Override finishActivityStacks(@onNull Set<IBinder> activityStackTokens)373 public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) { 374 if (activityStackTokens.isEmpty()) { 375 return; 376 } 377 synchronized (mLock) { 378 // Translate ActivityStack to TaskFragmentContainer. 379 final List<TaskFragmentContainer> pendingFinishingContainers = 380 activityStackTokens.stream() 381 .map(token -> { 382 synchronized (mLock) { 383 return getContainer(token); 384 } 385 }).filter(Objects::nonNull) 386 .toList(); 387 388 if (pendingFinishingContainers.isEmpty()) { 389 return; 390 } 391 // Start transaction with close transit type. 392 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 393 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 394 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 395 396 forAllTaskContainers(taskContainer -> { 397 synchronized (mLock) { 398 final List<TaskFragmentContainer> containers = 399 taskContainer.getTaskFragmentContainers(); 400 // Clean up the TaskFragmentContainers by the z-order from the lowest. 401 for (int i = 0; i < containers.size(); i++) { 402 final TaskFragmentContainer container = containers.get(i); 403 if (pendingFinishingContainers.contains(container)) { 404 // Don't update records here to prevent double invocation. 405 container.finish(false /* shouldFinishDependant */, mPresenter, 406 wct, this, false /* shouldRemoveRecord */); 407 } 408 } 409 // Remove container records. 410 removeContainers(taskContainer, pendingFinishingContainers); 411 // Update the change to the server side. 412 updateContainersInTaskIfVisible(wct, taskContainer.getTaskId()); 413 } 414 }); 415 416 // Apply the transaction. 417 transactionRecord.apply(false /* shouldApplyIndependently */); 418 } 419 } 420 421 @Override invalidateTopVisibleSplitAttributes()422 public void invalidateTopVisibleSplitAttributes() { 423 synchronized (mLock) { 424 WindowContainerTransaction wct = mTransactionManager.startNewTransaction() 425 .getTransaction(); 426 forAllTaskContainers(taskContainer -> { 427 synchronized (mLock) { 428 updateContainersInTaskIfVisible(wct, taskContainer.getTaskId()); 429 } 430 }); 431 mTransactionManager.getCurrentTransactionRecord() 432 .apply(false /* shouldApplyIndependently */); 433 } 434 } 435 436 @GuardedBy("mLock") forAllTaskContainers(@onNull Consumer<TaskContainer> callback)437 private void forAllTaskContainers(@NonNull Consumer<TaskContainer> callback) { 438 for (int i = mTaskContainers.size() - 1; i >= 0; --i) { 439 callback.accept(mTaskContainers.valueAt(i)); 440 } 441 } 442 443 @Override updateSplitAttributes(@onNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes)444 public void updateSplitAttributes(@NonNull IBinder splitInfoToken, 445 @NonNull SplitAttributes splitAttributes) { 446 Objects.requireNonNull(splitInfoToken); 447 Objects.requireNonNull(splitAttributes); 448 synchronized (mLock) { 449 final SplitContainer splitContainer = getSplitContainer(splitInfoToken); 450 if (splitContainer == null) { 451 Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken); 452 return; 453 } 454 // Override the default split Attributes so that it will be applied 455 // if the SplitContainer is not visible currently. 456 splitContainer.updateDefaultSplitAttributes(splitAttributes); 457 458 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 459 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 460 if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) { 461 transactionRecord.apply(false /* shouldApplyIndependently */); 462 } else { 463 // Abort if the SplitContainer wasn't updated. 464 transactionRecord.abort(); 465 } 466 } 467 } 468 469 /** 470 * Called when the transaction is ready so that the organizer can update the TaskFragments based 471 * on the changes in transaction. 472 */ 473 @Override onTransactionReady(@onNull TaskFragmentTransaction transaction)474 public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { 475 synchronized (mLock) { 476 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction( 477 transaction.getTransactionToken()); 478 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 479 final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); 480 for (TaskFragmentTransaction.Change change : changes) { 481 final int taskId = change.getTaskId(); 482 final TaskFragmentInfo info = change.getTaskFragmentInfo(); 483 switch (change.getType()) { 484 case TYPE_TASK_FRAGMENT_APPEARED: 485 mPresenter.updateTaskFragmentInfo(info); 486 onTaskFragmentAppeared(wct, info); 487 break; 488 case TYPE_TASK_FRAGMENT_INFO_CHANGED: 489 mPresenter.updateTaskFragmentInfo(info); 490 onTaskFragmentInfoChanged(wct, info); 491 break; 492 case TYPE_TASK_FRAGMENT_VANISHED: 493 mPresenter.removeTaskFragmentInfo(info); 494 onTaskFragmentVanished(wct, info); 495 break; 496 case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: 497 onTaskFragmentParentInfoChanged(wct, taskId, 498 change.getTaskFragmentParentInfo()); 499 break; 500 case TYPE_TASK_FRAGMENT_ERROR: 501 final Bundle errorBundle = change.getErrorBundle(); 502 final IBinder errorToken = change.getErrorCallbackToken(); 503 final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable( 504 KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class); 505 final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE); 506 final Throwable exception = errorBundle.getSerializable( 507 KEY_ERROR_CALLBACK_THROWABLE, Throwable.class); 508 if (errorTaskFragmentInfo != null) { 509 mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo); 510 } 511 onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType, 512 exception); 513 break; 514 case TYPE_ACTIVITY_REPARENTED_TO_TASK: 515 onActivityReparentedToTask( 516 wct, 517 taskId, 518 change.getActivityIntent(), 519 change.getActivityToken()); 520 break; 521 default: 522 throw new IllegalArgumentException( 523 "Unknown TaskFragmentEvent=" + change.getType()); 524 } 525 } 526 527 // Notify the server, and the server should apply and merge the 528 // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction. 529 transactionRecord.apply(false /* shouldApplyIndependently */); 530 updateCallbackIfNecessary(); 531 } 532 } 533 534 /** 535 * Called when a TaskFragment is created and organized by this organizer. 536 * 537 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 538 * @param taskFragmentInfo Info of the TaskFragment that is created. 539 */ 540 // Suppress GuardedBy warning because lint ask to mark this method as 541 // @GuardedBy(container.mController.mLock), which is mLock itself 542 @SuppressWarnings("GuardedBy") 543 @VisibleForTesting 544 @GuardedBy("mLock") onTaskFragmentAppeared(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)545 void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, 546 @NonNull TaskFragmentInfo taskFragmentInfo) { 547 final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); 548 if (container == null) { 549 return; 550 } 551 552 container.setInfo(wct, taskFragmentInfo); 553 if (container.isFinished()) { 554 mTransactionManager.getCurrentTransactionRecord() 555 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 556 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 557 } else { 558 // Update with the latest Task configuration. 559 updateContainer(wct, container); 560 } 561 } 562 563 /** 564 * Called when the status of an organized TaskFragment is changed. 565 * 566 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 567 * @param taskFragmentInfo Info of the TaskFragment that is changed. 568 */ 569 // Suppress GuardedBy warning because lint ask to mark this method as 570 // @GuardedBy(container.mController.mLock), which is mLock itself 571 @SuppressWarnings("GuardedBy") 572 @VisibleForTesting 573 @GuardedBy("mLock") onTaskFragmentInfoChanged(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)574 void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, 575 @NonNull TaskFragmentInfo taskFragmentInfo) { 576 final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); 577 if (container == null) { 578 return; 579 } 580 581 final boolean wasInPip = isInPictureInPicture(container); 582 container.setInfo(wct, taskFragmentInfo); 583 final boolean isInPip = isInPictureInPicture(container); 584 // Check if there are no running activities - consider the container empty if there are 585 // no non-finishing activities left. 586 if (!taskFragmentInfo.hasRunningActivity()) { 587 if (taskFragmentInfo.isTaskFragmentClearedForPip()) { 588 // Do not finish the dependents if the last activity is reparented to PiP. 589 // Instead, the original split should be cleanup, and the dependent may be 590 // expanded to fullscreen. 591 mTransactionManager.getCurrentTransactionRecord() 592 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 593 cleanupForEnterPip(wct, container); 594 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 595 } else if (taskFragmentInfo.isTaskClearedForReuse()) { 596 // Do not finish the dependents if this TaskFragment was cleared due to 597 // launching activity in the Task. 598 mTransactionManager.getCurrentTransactionRecord() 599 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 600 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 601 } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) { 602 // Do not finish the dependents if this TaskFragment was cleared to reorder 603 // the launching Activity to front of the Task. 604 mTransactionManager.getCurrentTransactionRecord() 605 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 606 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 607 } else if (!container.isWaitingActivityAppear()) { 608 // Do not finish the container before the expected activity appear until 609 // timeout. 610 mTransactionManager.getCurrentTransactionRecord() 611 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 612 mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); 613 } 614 } else if (wasInPip && isInPip) { 615 // No update until exit PIP. 616 return; 617 } else if (isInPip) { 618 // Enter PIP. 619 // All overrides will be cleanup. 620 container.setLastRequestedBounds(null /* bounds */); 621 container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED); 622 container.clearLastAdjacentTaskFragment(); 623 container.setLastCompanionTaskFragment(null /* fragmentToken */); 624 container.setLastRequestAnimationParams(TaskFragmentAnimationParams.DEFAULT); 625 cleanupForEnterPip(wct, container); 626 } else if (wasInPip) { 627 // Exit PIP. 628 // Updates the presentation of the container. Expand or launch placeholder if 629 // needed. 630 updateContainer(wct, container); 631 } 632 } 633 634 /** 635 * Called when an organized TaskFragment is removed. 636 * 637 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 638 * @param taskFragmentInfo Info of the TaskFragment that is removed. 639 */ 640 @VisibleForTesting 641 @GuardedBy("mLock") onTaskFragmentVanished(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo)642 void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, 643 @NonNull TaskFragmentInfo taskFragmentInfo) { 644 final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); 645 if (container != null) { 646 // Cleanup if the TaskFragment vanished is not requested by the organizer. 647 removeContainer(container); 648 // Make sure the containers in the Task are up-to-date. 649 updateContainersInTaskIfVisible(wct, container.getTaskId()); 650 } 651 cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); 652 } 653 654 /** 655 * Called when the parent leaf Task of organized TaskFragments is changed. 656 * When the leaf Task is changed, the organizer may want to update the TaskFragments in one 657 * transaction. 658 * 659 * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged} 660 * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there 661 * can be an override bounds. 662 * 663 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 664 * @param taskId Id of the parent Task that is changed. 665 * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task. 666 */ 667 @VisibleForTesting 668 @GuardedBy("mLock") onTaskFragmentParentInfoChanged(@onNull WindowContainerTransaction wct, int taskId, @NonNull TaskFragmentParentInfo parentInfo)669 void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, 670 int taskId, @NonNull TaskFragmentParentInfo parentInfo) { 671 final TaskContainer taskContainer = getTaskContainer(taskId); 672 if (taskContainer == null || taskContainer.isEmpty()) { 673 Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId); 674 return; 675 } 676 taskContainer.updateTaskFragmentParentInfo(parentInfo); 677 if (!taskContainer.isVisible()) { 678 // Don't update containers if the task is not visible. We only update containers when 679 // parentInfo#isVisibleRequested is true. 680 return; 681 } 682 if (isInPictureInPicture(parentInfo.getConfiguration())) { 683 // No need to update presentation in PIP until the Task exit PIP. 684 return; 685 } 686 updateContainersInTask(wct, taskContainer); 687 } 688 689 @GuardedBy("mLock") updateContainersInTaskIfVisible(@onNull WindowContainerTransaction wct, int taskId)690 void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { 691 final TaskContainer taskContainer = getTaskContainer(taskId); 692 if (taskContainer != null && taskContainer.isVisible()) { 693 updateContainersInTask(wct, taskContainer); 694 } 695 } 696 697 @GuardedBy("mLock") updateContainersInTask(@onNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer)698 private void updateContainersInTask(@NonNull WindowContainerTransaction wct, 699 @NonNull TaskContainer taskContainer) { 700 // Update all TaskFragments in the Task. Make a copy of the list since some may be 701 // removed on updating. 702 final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); 703 for (int i = containers.size() - 1; i >= 0; i--) { 704 final TaskFragmentContainer container = containers.get(i); 705 // Wait until onTaskFragmentAppeared to update new container. 706 if (!container.isFinished() && !container.isWaitingActivityAppear()) { 707 updateContainer(wct, container); 708 } 709 } 710 } 711 712 /** 713 * Called when an Activity is reparented to the Task with organized TaskFragment. For example, 714 * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its 715 * original Task. In this case, we need to notify the organizer so that it can check if the 716 * Activity matches any split rule. 717 * 718 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 719 * @param taskId The Task that the activity is reparented to. 720 * @param activityIntent The intent that the activity is original launched with. 721 * @param activityToken If the activity belongs to the same process as the organizer, this 722 * will be the actual activity token; if the activity belongs to a 723 * different process, the server will generate a temporary token that 724 * the organizer can use to reparent the activity through 725 * {@link WindowContainerTransaction} if needed. 726 */ 727 @VisibleForTesting 728 @GuardedBy("mLock") onActivityReparentedToTask(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken)729 void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, 730 int taskId, @NonNull Intent activityIntent, 731 @NonNull IBinder activityToken) { 732 // If the activity belongs to the current app process, we treat it as a new activity 733 // launch. 734 final Activity activity = getActivity(activityToken); 735 if (activity != null) { 736 // We don't allow split as primary for new launch because we currently only support 737 // launching to top. We allow split as primary for activity reparent because the 738 // activity may be split as primary before it is reparented out. In that case, we 739 // want to show it as primary again when it is reparented back. 740 if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) { 741 // When there is no embedding rule matched, try to place it in the top container 742 // like a normal launch. 743 placeActivityInTopContainer(wct, activity); 744 } 745 return; 746 } 747 748 final TaskContainer taskContainer = getTaskContainer(taskId); 749 if (taskContainer == null || taskContainer.isInPictureInPicture()) { 750 // We don't embed activity when it is in PIP. 751 return; 752 } 753 754 // If the activity belongs to a different app process, we treat it as starting new 755 // intent, since both actions might result in a new activity that should appear in an 756 // organized TaskFragment. 757 TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId, 758 activityIntent, null /* launchingActivity */); 759 if (targetContainer == null) { 760 // When there is no embedding rule matched, try to place it in the top container 761 // like a normal launch. 762 targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer(); 763 } 764 if (targetContainer == null) { 765 return; 766 } 767 wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), 768 activityToken); 769 // Because the activity does not belong to the organizer process, we wait until 770 // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). 771 } 772 773 /** 774 * Called when the {@link WindowContainerTransaction} created with 775 * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. 776 * 777 * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. 778 * @param errorCallbackToken token set in 779 * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} 780 * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no 781 * TaskFragment created. 782 * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed 783 * transaction operation. 784 * @param exception exception from the server side. 785 */ 786 // Suppress GuardedBy warning because lint ask to mark this method as 787 // @GuardedBy(container.mController.mLock), which is mLock itself 788 @SuppressWarnings("GuardedBy") 789 @VisibleForTesting 790 @GuardedBy("mLock") onTaskFragmentError(@onNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception)791 void onTaskFragmentError(@NonNull WindowContainerTransaction wct, 792 @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, 793 @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) { 794 Log.e(TAG, "onTaskFragmentError=" + exception.getMessage()); 795 switch (opType) { 796 case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: 797 case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { 798 final TaskFragmentContainer container; 799 if (taskFragmentInfo != null) { 800 container = getContainer(taskFragmentInfo.getFragmentToken()); 801 } else { 802 container = null; 803 } 804 if (container == null) { 805 break; 806 } 807 808 // Update the latest taskFragmentInfo and perform necessary clean-up 809 container.setInfo(wct, taskFragmentInfo); 810 container.clearPendingAppearedActivities(); 811 if (container.isEmpty()) { 812 mTransactionManager.getCurrentTransactionRecord() 813 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 814 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 815 } 816 break; 817 } 818 default: 819 Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo 820 + ", opType = " + opType); 821 } 822 } 823 824 /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ 825 @GuardedBy("mLock") cleanupTaskFragment(@onNull IBinder taskFragmentToken)826 private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { 827 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 828 final TaskContainer taskContainer = mTaskContainers.valueAt(i); 829 if (!taskContainer.mFinishedContainer.remove(taskFragmentToken)) { 830 continue; 831 } 832 if (taskContainer.isEmpty()) { 833 // Cleanup the TaskContainer if it becomes empty. 834 mTaskContainers.remove(taskContainer.getTaskId()); 835 } 836 return; 837 } 838 } 839 840 @VisibleForTesting 841 @GuardedBy("mLock") onActivityCreated(@onNull WindowContainerTransaction wct, @NonNull Activity launchedActivity)842 void onActivityCreated(@NonNull WindowContainerTransaction wct, 843 @NonNull Activity launchedActivity) { 844 resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */); 845 updateCallbackIfNecessary(); 846 } 847 848 /** 849 * Checks if the new added activity should be routed to a particular container. It can create a 850 * new container for the activity and a new split container if necessary. 851 * @param activity the activity that is newly added to the Task. 852 * @param isOnReparent whether the activity is reparented to the Task instead of new launched. 853 * We only support to split as primary for reparented activity for now. 854 * @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or 855 * in a state that the caller shouldn't handle. 856 */ 857 @VisibleForTesting 858 @GuardedBy("mLock") resolveActivityToContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnReparent)859 boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct, 860 @NonNull Activity activity, boolean isOnReparent) { 861 if (isInPictureInPicture(activity) || activity.isFinishing()) { 862 // We don't embed activity when it is in PIP, or finishing. Return true since we don't 863 // want any extra handling. 864 return true; 865 } 866 867 final TaskFragmentContainer container = getContainerWithActivity(activity); 868 if (!isOnReparent && container == null 869 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) { 870 // We can't find the new launched activity in any recorded container, but it is 871 // currently placed in an embedded TaskFragment. This can happen in two cases: 872 // 1. the activity is embedded in another app. 873 // 2. the organizer has already requested to remove the TaskFragment. 874 // In either case, return true since we don't want any extra handling. 875 Log.d(TAG, "Activity is in a TaskFragment that is not recorded by the organizer. r=" 876 + activity); 877 return true; 878 } 879 880 // Skip resolving if the activity is on a pinned TaskFragmentContainer. 881 // TODO(b/243518738): skip resolving for overlay container. 882 if (container != null) { 883 final TaskContainer taskContainer = container.getTaskContainer(); 884 if (taskContainer.isTaskFragmentContainerPinned(container)) { 885 return true; 886 } 887 } 888 889 final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null; 890 if (!isOnReparent && taskContainer != null 891 && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */) 892 != container) { 893 // Do not resolve if the launched activity is not the top-most container (excludes 894 // the pinned container) in the Task. 895 return true; 896 } 897 898 /* 899 * We will check the following to see if there is any embedding rule matched: 900 * 1. Whether the new launched activity should always expand. 901 * 2. Whether the new launched activity should launch a placeholder. 902 * 3. Whether the new launched activity has already been in a split with a rule matched 903 * (likely done in #onStartActivity). 904 * 4. Whether the activity below (if any) should be split with the new launched activity. 905 * 5. Whether the activity split with the activity below (if any) should be split with the 906 * new launched activity. 907 */ 908 909 // 1. Whether the new launched activity should always expand. 910 if (shouldExpand(activity, null /* intent */)) { 911 expandActivity(wct, activity); 912 return true; 913 } 914 915 // 2. Whether the new launched activity should launch a placeholder. 916 if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) { 917 return true; 918 } 919 920 // Skip resolving the following split-rules if the launched activity has been requested 921 // to be launched into its current container. 922 if (container != null && container.isActivityInRequestedTaskFragment( 923 activity.getActivityToken())) { 924 return true; 925 } 926 927 // 3. Whether the new launched activity has already been in a split with a rule matched. 928 if (isNewActivityInSplitWithRuleMatched(activity)) { 929 return true; 930 } 931 932 // 4. Whether the activity below (if any) should be split with the new launched activity. 933 final Activity activityBelow = findActivityBelow(activity); 934 if (activityBelow == null) { 935 // Can't find any activity below. 936 return false; 937 } 938 if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) { 939 // Have split rule of [ activityBelow | launchedActivity ]. 940 return true; 941 } 942 if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) { 943 // Have split rule of [ launchedActivity | activityBelow]. 944 return true; 945 } 946 947 // 5. Whether the activity split with the activity below (if any) should be split with the 948 // new launched activity. 949 final TaskFragmentContainer activityBelowContainer = getContainerWithActivity( 950 activityBelow); 951 final SplitContainer topSplit = getActiveSplitForContainer(activityBelowContainer); 952 if (topSplit == null || !isTopMostSplit(topSplit)) { 953 // Skip if it is not the topmost split. 954 return false; 955 } 956 final TaskFragmentContainer otherTopContainer = 957 topSplit.getPrimaryContainer() == activityBelowContainer 958 ? topSplit.getSecondaryContainer() 959 : topSplit.getPrimaryContainer(); 960 final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity(); 961 if (otherTopActivity == null || otherTopActivity == activity) { 962 // Can't find the top activity on the other split TaskFragment. 963 return false; 964 } 965 if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) { 966 // Have split rule of [ otherTopActivity | launchedActivity ]. 967 return true; 968 } 969 // Have split rule of [ launchedActivity | otherTopActivity]. 970 return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity); 971 } 972 973 /** 974 * Places the given activity to the top most TaskFragment in the task if there is any. 975 */ 976 @GuardedBy("mLock") 977 @VisibleForTesting placeActivityInTopContainer(@onNull WindowContainerTransaction wct, @NonNull Activity activity)978 void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct, 979 @NonNull Activity activity) { 980 if (getContainerWithActivity(activity) != null) { 981 // The activity has already been put in a TaskFragment. This is likely to be done by 982 // the server when the activity is started. 983 return; 984 } 985 final int taskId = getTaskId(activity); 986 final TaskContainer taskContainer = getTaskContainer(taskId); 987 if (taskContainer == null) { 988 return; 989 } 990 final TaskFragmentContainer targetContainer = 991 taskContainer.getTopNonFinishingTaskFragmentContainer(); 992 if (targetContainer == null) { 993 return; 994 } 995 targetContainer.addPendingAppearedActivity(activity); 996 wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), 997 activity.getActivityToken()); 998 } 999 1000 /** 1001 * Starts an activity to side of the launchingActivity with the provided split config. 1002 */ 1003 // Suppress GuardedBy warning because lint ask to mark this method as 1004 // @GuardedBy(container.mController.mLock), which is mLock itself 1005 @SuppressWarnings("GuardedBy") 1006 @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)1007 private void startActivityToSide(@NonNull WindowContainerTransaction wct, 1008 @NonNull Activity launchingActivity, @NonNull Intent intent, 1009 @Nullable Bundle options, @NonNull SplitRule sideRule, 1010 @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback, 1011 boolean isPlaceholder) { 1012 try { 1013 mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule, 1014 splitAttributes, isPlaceholder); 1015 } catch (Exception e) { 1016 if (failureCallback != null) { 1017 failureCallback.accept(e); 1018 } 1019 } 1020 } 1021 1022 /** 1023 * Expands the given activity by either expanding the TaskFragment it is currently in or putting 1024 * it into a new expanded TaskFragment. 1025 */ 1026 @GuardedBy("mLock") expandActivity(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1027 private void expandActivity(@NonNull WindowContainerTransaction wct, 1028 @NonNull Activity activity) { 1029 final TaskFragmentContainer container = getContainerWithActivity(activity); 1030 if (shouldContainerBeExpanded(container)) { 1031 // Make sure that the existing container is expanded. 1032 mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken()); 1033 } else { 1034 // Put activity into a new expanded container. 1035 final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity)); 1036 mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity); 1037 } 1038 } 1039 1040 /** Whether the given new launched activity is in a split with a rule matched. */ 1041 // Suppress GuardedBy warning because lint asks to mark this method as 1042 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1043 @SuppressWarnings("GuardedBy") 1044 @GuardedBy("mLock") isNewActivityInSplitWithRuleMatched(@onNull Activity launchedActivity)1045 private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) { 1046 final TaskFragmentContainer container = getContainerWithActivity(launchedActivity); 1047 final SplitContainer splitContainer = getActiveSplitForContainer(container); 1048 if (splitContainer == null) { 1049 return false; 1050 } 1051 1052 if (container == splitContainer.getPrimaryContainer()) { 1053 // The new launched can be in the primary container when it is starting a new activity 1054 // onCreate. 1055 final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); 1056 final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent(); 1057 if (secondaryIntent != null) { 1058 // Check with the pending Intent before it is started on the server side. 1059 // This can happen if the launched Activity start a new Intent to secondary during 1060 // #onCreated(). 1061 return getSplitRule(launchedActivity, secondaryIntent) != null; 1062 } 1063 final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity(); 1064 return secondaryActivity != null 1065 && getSplitRule(launchedActivity, secondaryActivity) != null; 1066 } 1067 1068 // Check if the new launched activity is a placeholder. 1069 if (splitContainer.getSplitRule() instanceof SplitPlaceholderRule) { 1070 final SplitPlaceholderRule placeholderRule = 1071 (SplitPlaceholderRule) splitContainer.getSplitRule(); 1072 final ComponentName placeholderName = placeholderRule.getPlaceholderIntent() 1073 .getComponent(); 1074 // TODO(b/232330767): Do we have a better way to check this? 1075 return placeholderName == null 1076 || placeholderName.equals(launchedActivity.getComponentName()) 1077 || placeholderRule.getPlaceholderIntent().equals(launchedActivity.getIntent()); 1078 } 1079 1080 // Check if the new launched activity should be split with the primary top activity. 1081 final Activity primaryActivity = splitContainer.getPrimaryContainer() 1082 .getTopNonFinishingActivity(); 1083 if (primaryActivity == null) { 1084 return false; 1085 } 1086 /* TODO(b/231845476) we should always respect clearTop. 1087 final SplitPairRule curSplitRule = (SplitPairRule) splitContainer.getSplitRule(); 1088 final SplitPairRule splitRule = getSplitRule(primaryActivity, launchedActivity); 1089 return splitRule != null && haveSamePresentation(splitRule, curSplitRule) 1090 // If the new launched split rule should clear top and it is not the bottom most, 1091 // it means we should create a new split pair and clear the existing secondary. 1092 && (!splitRule.shouldClearTop() 1093 || container.getBottomMostActivity() == launchedActivity); 1094 */ 1095 return getSplitRule(primaryActivity, launchedActivity) != null; 1096 } 1097 1098 /** Finds the activity below the given activity. */ 1099 @VisibleForTesting 1100 @Nullable 1101 @GuardedBy("mLock") findActivityBelow(@onNull Activity activity)1102 Activity findActivityBelow(@NonNull Activity activity) { 1103 Activity activityBelow = null; 1104 final TaskFragmentContainer container = getContainerWithActivity(activity); 1105 // Looking for the activity below from the information we already have if the container 1106 // only embeds activities of the same process because activities of other processes are not 1107 // available in this embedding host process for security concern. 1108 if (container != null && !container.hasCrossProcessActivities()) { 1109 final List<Activity> containerActivities = container.collectNonFinishingActivities(); 1110 final int index = containerActivities.indexOf(activity); 1111 if (index > 0) { 1112 activityBelow = containerActivities.get(index - 1); 1113 } 1114 } 1115 if (activityBelow == null) { 1116 final IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow( 1117 activity.getActivityToken()); 1118 if (belowToken != null) { 1119 activityBelow = getActivity(belowToken); 1120 } 1121 } 1122 return activityBelow; 1123 } 1124 1125 /** 1126 * Checks if there is a rule to split the two activities. If there is one, puts them into split 1127 * and returns {@code true}. Otherwise, returns {@code false}. 1128 */ 1129 // Suppress GuardedBy warning because lint ask to mark this method as 1130 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1131 @SuppressWarnings("GuardedBy") 1132 @GuardedBy("mLock") putActivitiesIntoSplitIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity)1133 private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct, 1134 @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { 1135 final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity); 1136 if (splitRule == null) { 1137 return false; 1138 } 1139 final TaskFragmentContainer primaryContainer = getContainerWithActivity( 1140 primaryActivity); 1141 final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); 1142 final TaskContainer.TaskProperties taskProperties = mPresenter 1143 .getTaskProperties(primaryActivity); 1144 final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( 1145 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), 1146 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity)); 1147 if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() 1148 && canReuseContainer(splitRule, splitContainer.getSplitRule(), 1149 getTaskWindowMetrics(taskProperties.getConfiguration()), 1150 calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) { 1151 // Can launch in the existing secondary container if the rules share the same 1152 // presentation. 1153 final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); 1154 if (secondaryContainer == getContainerWithActivity(secondaryActivity)) { 1155 // The activity is already in the target TaskFragment. 1156 return true; 1157 } 1158 secondaryContainer.addPendingAppearedActivity(secondaryActivity); 1159 if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, 1160 secondaryActivity, null /* secondaryIntent */) 1161 != RESULT_EXPAND_FAILED_NO_TF_INFO) { 1162 wct.reparentActivityToTaskFragment( 1163 secondaryContainer.getTaskFragmentToken(), 1164 secondaryActivity.getActivityToken()); 1165 return true; 1166 } 1167 } 1168 // Create new split pair. 1169 mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule, 1170 calculatedSplitAttributes); 1171 return true; 1172 } 1173 1174 @GuardedBy("mLock") onActivityConfigurationChanged(@onNull WindowContainerTransaction wct, @NonNull Activity activity)1175 private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct, 1176 @NonNull Activity activity) { 1177 if (activity.isFinishing()) { 1178 // Do nothing if the activity is currently finishing. 1179 return; 1180 } 1181 1182 if (isInPictureInPicture(activity)) { 1183 // We don't embed activity when it is in PIP. 1184 return; 1185 } 1186 final TaskFragmentContainer currentContainer = getContainerWithActivity(activity); 1187 1188 if (currentContainer != null) { 1189 // Changes to activities in controllers are handled in 1190 // onTaskFragmentParentInfoChanged 1191 return; 1192 } 1193 1194 // Check if activity requires a placeholder 1195 launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */); 1196 } 1197 1198 @VisibleForTesting 1199 @GuardedBy("mLock") onActivityDestroyed(@onNull Activity activity)1200 void onActivityDestroyed(@NonNull Activity activity) { 1201 if (!activity.isFinishing()) { 1202 // onDestroyed is triggered without finishing. This happens when the activity is 1203 // relaunched. In this case, we don't want to cleanup the record. 1204 return; 1205 } 1206 // Remove any pending appeared activity, as the server won't send finished activity to the 1207 // organizer. 1208 final IBinder activityToken = activity.getActivityToken(); 1209 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1210 mTaskContainers.valueAt(i).onActivityDestroyed(activityToken); 1211 } 1212 // We didn't trigger the callback if there were any pending appeared activities, so check 1213 // again after the pending is removed. 1214 updateCallbackIfNecessary(); 1215 } 1216 1217 /** 1218 * Called when we have been waiting too long for the TaskFragment to become non-empty after 1219 * creation. 1220 */ 1221 @GuardedBy("mLock") onTaskFragmentAppearEmptyTimeout(@onNull TaskFragmentContainer container)1222 void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) { 1223 final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); 1224 onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container); 1225 // Can be applied independently as a timeout callback. 1226 transactionRecord.apply(true /* shouldApplyIndependently */); 1227 } 1228 1229 /** 1230 * Called when we have been waiting too long for the TaskFragment to become non-empty after 1231 * creation. 1232 */ 1233 @GuardedBy("mLock") onTaskFragmentAppearEmptyTimeout(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1234 void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, 1235 @NonNull TaskFragmentContainer container) { 1236 mTransactionManager.getCurrentTransactionRecord() 1237 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 1238 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); 1239 } 1240 1241 @Nullable 1242 @GuardedBy("mLock") resolveStartActivityIntentFromNonActivityContext( @onNull WindowContainerTransaction wct, @NonNull Intent intent)1243 private TaskFragmentContainer resolveStartActivityIntentFromNonActivityContext( 1244 @NonNull WindowContainerTransaction wct, @NonNull Intent intent) { 1245 final int taskCount = mTaskContainers.size(); 1246 if (taskCount == 0) { 1247 // We don't have other Activity to check split with. 1248 return null; 1249 } 1250 if (taskCount > 1) { 1251 Log.w(TAG, "App is calling startActivity from a non-Activity context when it has" 1252 + " more than one Task. If the new launch Activity is in a different process," 1253 + " and it is expected to be embedded, please start it from an Activity" 1254 + " instead."); 1255 return null; 1256 } 1257 1258 // Check whether the Intent should be embedded in the known Task. 1259 final TaskContainer taskContainer = mTaskContainers.valueAt(0); 1260 if (taskContainer.isInPictureInPicture() 1261 || taskContainer.getTopNonFinishingActivity() == null) { 1262 // We don't embed activity when it is in PIP, or if we can't find any other owner 1263 // activity in the Task. 1264 return null; 1265 } 1266 1267 return resolveStartActivityIntent(wct, taskContainer.getTaskId(), intent, 1268 null /* launchingActivity */); 1269 } 1270 1271 /** 1272 * When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer} 1273 * that we should reparent the new activity to if there is any embedding rule matched. 1274 * 1275 * @param wct {@link WindowContainerTransaction} including all the window change 1276 * requests. The caller is responsible to call 1277 * {@link android.window.TaskFragmentOrganizer#applyTransaction}. 1278 * @param taskId The Task to start the activity in. 1279 * @param intent The {@link Intent} for starting the new launched activity. 1280 * @param launchingActivity The {@link Activity} that starts the new activity. We will 1281 * prioritize to split the new activity with it if it is not 1282 * {@code null}. 1283 * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there 1284 * is no embedding rule matched. 1285 */ 1286 @VisibleForTesting 1287 @Nullable 1288 @GuardedBy("mLock") resolveStartActivityIntent(@onNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity)1289 TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, 1290 int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { 1291 // Skip resolving if started from pinned TaskFragmentContainer. 1292 // TODO(b/243518738): skip resolving for overlay container. 1293 if (launchingActivity != null) { 1294 final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity( 1295 launchingActivity); 1296 final TaskContainer taskContainer = 1297 taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null; 1298 if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned( 1299 taskFragmentContainer)) { 1300 return null; 1301 } 1302 } 1303 1304 /* 1305 * We will check the following to see if there is any embedding rule matched: 1306 * 1. Whether the new activity intent should always expand. 1307 * 2. Whether the launching activity (if set) should be split with the new activity intent. 1308 * 3. Whether the top activity (if any) should be split with the new activity intent. 1309 * 4. Whether the top activity (if any) in other split should be split with the new 1310 * activity intent. 1311 */ 1312 1313 // 1. Whether the new activity intent should always expand. 1314 if (shouldExpand(null /* activity */, intent)) { 1315 return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity); 1316 } 1317 1318 // 2. Whether the launching activity (if set) should be split with the new activity intent. 1319 if (launchingActivity != null) { 1320 final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, 1321 launchingActivity, intent, true /* respectClearTop */); 1322 if (container != null) { 1323 return container; 1324 } 1325 } 1326 1327 // 3. Whether the top activity (if any) should be split with the new activity intent. 1328 final TaskContainer taskContainer = getTaskContainer(taskId); 1329 if (taskContainer == null 1330 || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) { 1331 // There is no other activity in the Task to check split with. 1332 return null; 1333 } 1334 final TaskFragmentContainer topContainer = 1335 taskContainer.getTopNonFinishingTaskFragmentContainer(); 1336 final Activity topActivity = topContainer.getTopNonFinishingActivity(); 1337 if (topActivity != null && topActivity != launchingActivity) { 1338 final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, 1339 topActivity, intent, false /* respectClearTop */); 1340 if (container != null) { 1341 return container; 1342 } 1343 } 1344 1345 // 4. Whether the top activity (if any) in other split should be split with the new 1346 // activity intent. 1347 final SplitContainer topSplit = getActiveSplitForContainer(topContainer); 1348 if (topSplit == null) { 1349 return null; 1350 } 1351 final TaskFragmentContainer otherTopContainer = 1352 topSplit.getPrimaryContainer() == topContainer 1353 ? topSplit.getSecondaryContainer() 1354 : topSplit.getPrimaryContainer(); 1355 final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity(); 1356 if (otherTopActivity != null && otherTopActivity != launchingActivity) { 1357 return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent, 1358 false /* respectClearTop */); 1359 } 1360 return null; 1361 } 1362 1363 /** 1364 * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into. 1365 */ 1366 @GuardedBy("mLock") 1367 @Nullable createEmptyExpandedContainer( @onNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity)1368 private TaskFragmentContainer createEmptyExpandedContainer( 1369 @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, 1370 @Nullable Activity launchingActivity) { 1371 // We need an activity in the organizer process in the same Task to use as the owner 1372 // activity, as well as to get the Task window info. 1373 final Activity activityInTask; 1374 if (launchingActivity != null) { 1375 activityInTask = launchingActivity; 1376 } else { 1377 final TaskContainer taskContainer = getTaskContainer(taskId); 1378 activityInTask = taskContainer != null 1379 ? taskContainer.getTopNonFinishingActivity() 1380 : null; 1381 } 1382 if (activityInTask == null) { 1383 // Can't find any activity in the Task that we can use as the owner activity. 1384 return null; 1385 } 1386 final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask, 1387 taskId); 1388 mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(), 1389 activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); 1390 mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(), 1391 TaskFragmentAnimationParams.DEFAULT); 1392 return expandedContainer; 1393 } 1394 1395 /** 1396 * Returns a container for the new activity intent to launch into as splitting with the primary 1397 * activity. 1398 */ 1399 @GuardedBy("mLock") 1400 @Nullable getSecondaryContainerForSplitIfAny( @onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent intent, boolean respectClearTop)1401 private TaskFragmentContainer getSecondaryContainerForSplitIfAny( 1402 @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, 1403 @NonNull Intent intent, boolean respectClearTop) { 1404 final SplitPairRule splitRule = getSplitRule(primaryActivity, intent); 1405 if (splitRule == null) { 1406 return null; 1407 } 1408 final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); 1409 final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); 1410 final TaskContainer.TaskProperties taskProperties = mPresenter 1411 .getTaskProperties(primaryActivity); 1412 final WindowMetrics taskWindowMetrics = getTaskWindowMetrics( 1413 taskProperties.getConfiguration()); 1414 final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( 1415 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), 1416 getActivityIntentMinDimensionsPair(primaryActivity, intent)); 1417 if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() 1418 && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics, 1419 calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes()) 1420 // TODO(b/231845476) we should always respect clearTop. 1421 || !respectClearTop) 1422 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, 1423 null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) { 1424 // Can launch in the existing secondary container if the rules share the same 1425 // presentation. 1426 return splitContainer.getSecondaryContainer(); 1427 } 1428 // Create a new TaskFragment to split with the primary activity for the new activity. 1429 return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, 1430 splitRule, calculatedSplitAttributes); 1431 } 1432 1433 /** 1434 * Returns a container that this activity is registered with. An activity can only belong to one 1435 * container, or no container at all. 1436 */ 1437 @GuardedBy("mLock") 1438 @Nullable getContainerWithActivity(@onNull Activity activity)1439 TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) { 1440 return getContainerWithActivity(activity.getActivityToken()); 1441 } 1442 1443 @GuardedBy("mLock") 1444 @Nullable getContainerWithActivity(@onNull IBinder activityToken)1445 TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { 1446 // Check pending appeared activity first because there can be a delay for the server 1447 // update. 1448 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1449 final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) 1450 .getTaskFragmentContainers(); 1451 for (int j = containers.size() - 1; j >= 0; j--) { 1452 final TaskFragmentContainer container = containers.get(j); 1453 if (container.hasPendingAppearedActivity(activityToken)) { 1454 return container; 1455 } 1456 } 1457 } 1458 1459 // Check appeared activity if there is no such pending appeared activity. 1460 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1461 final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) 1462 .getTaskFragmentContainers(); 1463 for (int j = containers.size() - 1; j >= 0; j--) { 1464 final TaskFragmentContainer container = containers.get(j); 1465 if (container.hasAppearedActivity(activityToken)) { 1466 return container; 1467 } 1468 } 1469 } 1470 return null; 1471 } 1472 1473 @GuardedBy("mLock") newContainer(@onNull Activity pendingAppearedActivity, int taskId)1474 TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) { 1475 return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); 1476 } 1477 1478 @GuardedBy("mLock") newContainer(@onNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId)1479 TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, 1480 @NonNull Activity activityInTask, int taskId) { 1481 return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, 1482 activityInTask, taskId, null /* pairedPrimaryContainer */); 1483 } 1484 1485 @GuardedBy("mLock") newContainer(@onNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId)1486 TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, 1487 @NonNull Activity activityInTask, int taskId) { 1488 return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, 1489 activityInTask, taskId, null /* pairedPrimaryContainer */); 1490 } 1491 1492 /** 1493 * Creates and registers a new organized container with an optional activity that will be 1494 * re-parented to it in a WCT. 1495 * 1496 * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. 1497 * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. 1498 * @param activityInTask activity in the same Task so that we can get the Task bounds 1499 * if needed. 1500 * @param taskId parent Task of the new TaskFragment. 1501 * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is 1502 * set, the new container will be added right above it. 1503 */ 1504 @GuardedBy("mLock") newContainer(@ullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, @Nullable TaskFragmentContainer pairedPrimaryContainer)1505 TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, 1506 @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, 1507 @Nullable TaskFragmentContainer pairedPrimaryContainer) { 1508 if (activityInTask == null) { 1509 throw new IllegalArgumentException("activityInTask must not be null,"); 1510 } 1511 if (!mTaskContainers.contains(taskId)) { 1512 mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask)); 1513 } 1514 final TaskContainer taskContainer = mTaskContainers.get(taskId); 1515 final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, 1516 pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer); 1517 return container; 1518 } 1519 1520 /** 1521 * Creates and registers a new split with the provided containers and configuration. Finishes 1522 * existing secondary containers if found for the given primary container. 1523 */ 1524 // Suppress GuardedBy warning because lint ask to mark this method as 1525 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1526 @SuppressWarnings("GuardedBy") 1527 @GuardedBy("mLock") registerSplit(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes)1528 void registerSplit(@NonNull WindowContainerTransaction wct, 1529 @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, 1530 @NonNull TaskFragmentContainer secondaryContainer, 1531 @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) { 1532 final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, 1533 secondaryContainer, splitRule, splitAttributes); 1534 // Remove container later to prevent pinning escaping toast showing in lock task mode. 1535 if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { 1536 removeExistingSecondaryContainers(wct, primaryContainer); 1537 } 1538 primaryContainer.getTaskContainer().addSplitContainer(splitContainer); 1539 } 1540 1541 /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ 1542 @GuardedBy("mLock") cleanupForEnterPip(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1543 private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct, 1544 @NonNull TaskFragmentContainer container) { 1545 final TaskContainer taskContainer = container.getTaskContainer(); 1546 if (taskContainer == null) { 1547 return; 1548 } 1549 final List<SplitContainer> splitsToRemove = new ArrayList<>(); 1550 final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); 1551 final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>(); 1552 for (SplitContainer splitContainer : splitContainers) { 1553 if (splitContainer.getPrimaryContainer() != container 1554 && splitContainer.getSecondaryContainer() != container) { 1555 continue; 1556 } 1557 splitsToRemove.add(splitContainer); 1558 final TaskFragmentContainer splitTf = splitContainer.getPrimaryContainer() == container 1559 ? splitContainer.getSecondaryContainer() 1560 : splitContainer.getPrimaryContainer(); 1561 containersToUpdate.add(splitTf); 1562 // We don't want the PIP TaskFragment to be removed as a result of any of its dependents 1563 // being removed. 1564 splitTf.removeContainerToFinishOnExit(container); 1565 if (container.getTopNonFinishingActivity() != null) { 1566 splitTf.removeActivityToFinishOnExit(container.getTopNonFinishingActivity()); 1567 } 1568 } 1569 container.resetDependencies(); 1570 taskContainer.removeSplitContainers(splitsToRemove); 1571 // If there is any TaskFragment split with the PIP TaskFragment, update their presentations 1572 // since the split is dismissed. 1573 // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. 1574 for (TaskFragmentContainer containerToUpdate : containersToUpdate) { 1575 updateContainer(wct, containerToUpdate); 1576 } 1577 } 1578 1579 /** 1580 * Removes the container from bookkeeping records. 1581 */ removeContainer(@onNull TaskFragmentContainer container)1582 void removeContainer(@NonNull TaskFragmentContainer container) { 1583 removeContainers(container.getTaskContainer(), Collections.singletonList(container)); 1584 } 1585 1586 /** 1587 * Removes containers from bookkeeping records. 1588 */ removeContainers(@onNull TaskContainer taskContainer, @NonNull List<TaskFragmentContainer> containers)1589 void removeContainers(@NonNull TaskContainer taskContainer, 1590 @NonNull List<TaskFragmentContainer> containers) { 1591 // Remove all split containers that included this one 1592 taskContainer.removeTaskFragmentContainers(containers); 1593 // Marked as a pending removal which will be removed after it is actually removed on the 1594 // server side (#onTaskFragmentVanished). 1595 // In this way, we can keep track of the Task bounds until we no longer have any 1596 // TaskFragment there. 1597 taskContainer.mFinishedContainer.addAll(containers.stream().map( 1598 TaskFragmentContainer::getTaskFragmentToken).toList()); 1599 1600 // Cleanup any split references. 1601 final List<SplitContainer> containersToRemove = new ArrayList<>(); 1602 final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); 1603 for (SplitContainer splitContainer : splitContainers) { 1604 if (containersToRemove.contains(splitContainer)) { 1605 // Don't need to check because it has been in the remove list. 1606 continue; 1607 } 1608 if (containers.stream().anyMatch(container -> 1609 splitContainer.getPrimaryContainer().equals(container) 1610 || splitContainer.getSecondaryContainer().equals(container))) { 1611 containersToRemove.add(splitContainer); 1612 } 1613 } 1614 taskContainer.removeSplitContainers(containersToRemove); 1615 1616 // Cleanup any dependent references. 1617 final List<TaskFragmentContainer> taskFragmentContainers = 1618 taskContainer.getTaskFragmentContainers(); 1619 for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) { 1620 containerToUpdate.removeContainersToFinishOnExit(containers); 1621 } 1622 } 1623 1624 /** 1625 * Removes a secondary container for the given primary container if an existing split is 1626 * already registered. 1627 */ 1628 // Suppress GuardedBy warning because lint asks to mark this method as 1629 // @GuardedBy(existingSplitContainer.getSecondaryContainer().mController.mLock), which is mLock 1630 // itself 1631 @SuppressWarnings("GuardedBy") 1632 @GuardedBy("mLock") removeExistingSecondaryContainers(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer)1633 private void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct, 1634 @NonNull TaskFragmentContainer primaryContainer) { 1635 // If the primary container was already in a split - remove the secondary container that 1636 // is now covered by the new one that replaced it. 1637 final SplitContainer existingSplitContainer = getActiveSplitForContainer( 1638 primaryContainer); 1639 if (existingSplitContainer == null 1640 || primaryContainer == existingSplitContainer.getSecondaryContainer()) { 1641 return; 1642 } 1643 1644 // If the secondary container is pinned, it should not be removed. 1645 final SplitContainer activeContainer = 1646 getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer()); 1647 if (activeContainer instanceof SplitPinContainer) { 1648 return; 1649 } 1650 1651 existingSplitContainer.getSecondaryContainer().finish( 1652 false /* shouldFinishDependent */, mPresenter, wct, this); 1653 } 1654 1655 /** 1656 * Returns the topmost not finished container in Task of given task id. 1657 */ 1658 @GuardedBy("mLock") 1659 @Nullable getTopActiveContainer(int taskId)1660 TaskFragmentContainer getTopActiveContainer(int taskId) { 1661 final TaskContainer taskContainer = mTaskContainers.get(taskId); 1662 if (taskContainer == null) { 1663 return null; 1664 } 1665 final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); 1666 for (int i = containers.size() - 1; i >= 0; i--) { 1667 final TaskFragmentContainer container = containers.get(i); 1668 if (!container.isFinished() && (container.getRunningActivityCount() > 0 1669 // We may be waiting for the top TaskFragment to become non-empty after 1670 // creation. In that case, we don't want to treat the TaskFragment below it as 1671 // top active, otherwise it may incorrectly launch placeholder on top of the 1672 // pending TaskFragment. 1673 || container.isWaitingActivityAppear())) { 1674 return container; 1675 } 1676 } 1677 return null; 1678 } 1679 1680 /** 1681 * Updates the presentation of the container. If the container is part of the split or should 1682 * have a placeholder, it will also update the other part of the split. 1683 */ 1684 @GuardedBy("mLock") updateContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1685 void updateContainer(@NonNull WindowContainerTransaction wct, 1686 @NonNull TaskFragmentContainer container) { 1687 if (!container.getTaskContainer().isVisible()) { 1688 // Wait until the Task is visible to avoid unnecessary update when the Task is still in 1689 // background. 1690 return; 1691 } 1692 1693 if (launchPlaceholderIfNecessary(wct, container)) { 1694 // Placeholder was launched, the positions will be updated when the activity is added 1695 // to the secondary container. 1696 return; 1697 } 1698 if (shouldContainerBeExpanded(container)) { 1699 if (container.getInfo() != null) { 1700 mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken()); 1701 } 1702 // If the info is not available yet the task fragment will be expanded when it's ready 1703 return; 1704 } 1705 final SplitContainer splitContainer = getActiveSplitForContainer(container); 1706 if (splitContainer == null) { 1707 return; 1708 } 1709 1710 updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */); 1711 } 1712 1713 /** 1714 * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the 1715 * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes} 1716 * are {@code null}, the {@link SplitAttributes} will be calculated with 1717 * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}. 1718 * 1719 * @param splitContainer The {@link SplitContainer} to update 1720 * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}. 1721 * Otherwise, use the value calculated by 1722 * {@link SplitPresenter#computeSplitAttributes( 1723 * TaskContainer.TaskProperties, SplitRule, Pair)} 1724 * 1725 * @return {@code true} if the update succeed. Otherwise, returns {@code false}. 1726 */ 1727 @VisibleForTesting 1728 @GuardedBy("mLock") updateSplitContainerIfNeeded(@onNull SplitContainer splitContainer, @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes)1729 boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer, 1730 @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) { 1731 if (!isTopMostSplit(splitContainer)) { 1732 // Skip position update - it isn't the topmost split. 1733 return false; 1734 } 1735 if (splitContainer.getPrimaryContainer().isFinished() 1736 || splitContainer.getSecondaryContainer().isFinished()) { 1737 // Skip position update - one or both containers are finished. 1738 return false; 1739 } 1740 if (splitAttributes == null) { 1741 final TaskContainer.TaskProperties taskProperties = splitContainer.getTaskContainer() 1742 .getTaskProperties(); 1743 final SplitRule splitRule = splitContainer.getSplitRule(); 1744 final SplitAttributes defaultSplitAttributes = splitContainer 1745 .getDefaultSplitAttributes(); 1746 final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); 1747 splitAttributes = mPresenter.computeSplitAttributes(taskProperties, splitRule, 1748 defaultSplitAttributes, minDimensionsPair); 1749 } 1750 splitContainer.updateCurrentSplitAttributes(splitAttributes); 1751 if (dismissPlaceholderIfNecessary(wct, splitContainer)) { 1752 // Placeholder was finished, the positions will be updated when its container is emptied 1753 return true; 1754 } 1755 mPresenter.updateSplitContainer(splitContainer, wct); 1756 return true; 1757 } 1758 1759 /** Whether the given split is the topmost split in the Task. */ isTopMostSplit(@onNull SplitContainer splitContainer)1760 private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) { 1761 final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer() 1762 .getTaskContainer().getSplitContainers(); 1763 return splitContainer == splitContainers.get(splitContainers.size() - 1); 1764 } 1765 1766 /** 1767 * Returns the top active split container that has the provided container, if available. 1768 */ 1769 @Nullable getActiveSplitForContainer(@ullable TaskFragmentContainer container)1770 private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) { 1771 if (container == null) { 1772 return null; 1773 } 1774 final List<SplitContainer> splitContainers = 1775 container.getTaskContainer().getSplitContainers(); 1776 if (splitContainers.isEmpty()) { 1777 return null; 1778 } 1779 for (int i = splitContainers.size() - 1; i >= 0; i--) { 1780 final SplitContainer splitContainer = splitContainers.get(i); 1781 if (container.equals(splitContainer.getSecondaryContainer()) 1782 || container.equals(splitContainer.getPrimaryContainer())) { 1783 return splitContainer; 1784 } 1785 } 1786 return null; 1787 } 1788 1789 /** 1790 * Returns the active split that has the provided containers as primary and secondary or as 1791 * secondary and primary, if available. 1792 */ 1793 @GuardedBy("mLock") 1794 @Nullable getActiveSplitForContainers( @onNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer)1795 SplitContainer getActiveSplitForContainers( 1796 @NonNull TaskFragmentContainer firstContainer, 1797 @NonNull TaskFragmentContainer secondContainer) { 1798 final List<SplitContainer> splitContainers = firstContainer.getTaskContainer() 1799 .getSplitContainers(); 1800 for (int i = splitContainers.size() - 1; i >= 0; i--) { 1801 final SplitContainer splitContainer = splitContainers.get(i); 1802 final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); 1803 final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer(); 1804 if ((firstContainer == secondary && secondContainer == primary) 1805 || (firstContainer == primary && secondContainer == secondary)) { 1806 return splitContainer; 1807 } 1808 } 1809 return null; 1810 } 1811 1812 /** 1813 * Checks if the container requires a placeholder and launches it if necessary. 1814 */ 1815 @GuardedBy("mLock") launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)1816 private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, 1817 @NonNull TaskFragmentContainer container) { 1818 final Activity topActivity = container.getTopNonFinishingActivity(); 1819 if (topActivity == null) { 1820 return false; 1821 } 1822 1823 return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */); 1824 } 1825 1826 // Suppress GuardedBy warning because lint ask to mark this method as 1827 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1828 @SuppressWarnings("GuardedBy") 1829 @GuardedBy("mLock") launchPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnCreated)1830 boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, 1831 @NonNull Activity activity, boolean isOnCreated) { 1832 if (activity.isFinishing()) { 1833 return false; 1834 } 1835 1836 final TaskFragmentContainer container = getContainerWithActivity(activity); 1837 if (container != null && !allowLaunchPlaceholder(container)) { 1838 // We don't allow activity in this TaskFragment to launch placeholder. 1839 return false; 1840 } 1841 1842 // Check if there is enough space for launch 1843 final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity); 1844 1845 if (placeholderRule == null) { 1846 return false; 1847 } 1848 1849 final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity); 1850 final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, 1851 placeholderRule.getPlaceholderIntent()); 1852 final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties, 1853 placeholderRule, placeholderRule.getDefaultSplitAttributes(), minDimensionsPair); 1854 if (!SplitPresenter.shouldShowSplit(splitAttributes)) { 1855 return false; 1856 } 1857 1858 // TODO(b/190433398): Handle failed request 1859 final Bundle options = getPlaceholderOptions(activity, isOnCreated); 1860 startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options, 1861 placeholderRule, splitAttributes, null /* failureCallback */, 1862 true /* isPlaceholder */); 1863 return true; 1864 } 1865 1866 /** Whether or not to allow activity in this container to launch placeholder. */ 1867 @GuardedBy("mLock") allowLaunchPlaceholder(@onNull TaskFragmentContainer container)1868 private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) { 1869 final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId()); 1870 if (container != topContainer) { 1871 // The container is not the top most. 1872 if (!container.isVisible()) { 1873 // In case the container is visible (the one on top may be transparent), we may 1874 // still want to launch placeholder even if it is not the top most. 1875 return false; 1876 } 1877 if (topContainer.isWaitingActivityAppear()) { 1878 // When the top container appeared info is not sent by the server yet, the visible 1879 // check above may not be reliable. 1880 return false; 1881 } 1882 } 1883 1884 final SplitContainer splitContainer = getActiveSplitForContainer(container); 1885 if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { 1886 // Don't launch placeholder for primary split container. 1887 return false; 1888 } 1889 if (splitContainer instanceof SplitPinContainer) { 1890 // Don't launch placeholder if pinned 1891 return false; 1892 } 1893 return true; 1894 } 1895 1896 /** 1897 * Gets the activity options for starting the placeholder activity. In case the placeholder is 1898 * launched when the Task is in the background, we don't want to bring the Task to the front. 1899 * @param primaryActivity the primary activity to launch the placeholder from. 1900 * @param isOnCreated whether this happens during the primary activity onCreated. 1901 */ 1902 @VisibleForTesting 1903 @GuardedBy("mLock") 1904 @Nullable getPlaceholderOptions(@onNull Activity primaryActivity, boolean isOnCreated)1905 Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) { 1906 // Setting avoid move to front will also skip the animation. We only want to do that when 1907 // the Task is currently in background. 1908 // Check if the primary is resumed or if this is called when the primary is onCreated 1909 // (not resumed yet). 1910 if (isOnCreated || primaryActivity.isResumed()) { 1911 // Only set trigger type if the launch happens in foreground. 1912 mTransactionManager.getCurrentTransactionRecord() 1913 .setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); 1914 return null; 1915 } 1916 final ActivityOptions options = ActivityOptions.makeBasic(); 1917 options.setAvoidMoveToFront(); 1918 return options.toBundle(); 1919 } 1920 1921 // Suppress GuardedBy warning because lint ask to mark this method as 1922 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 1923 @SuppressWarnings("GuardedBy") 1924 @VisibleForTesting 1925 @GuardedBy("mLock") dismissPlaceholderIfNecessary(@onNull WindowContainerTransaction wct, @NonNull SplitContainer splitContainer)1926 boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, 1927 @NonNull SplitContainer splitContainer) { 1928 if (!splitContainer.isPlaceholderContainer()) { 1929 return false; 1930 } 1931 1932 if (isStickyPlaceholderRule(splitContainer.getSplitRule())) { 1933 // The placeholder should remain after it was first shown. 1934 return false; 1935 } 1936 final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes(); 1937 if (SplitPresenter.shouldShowSplit(splitAttributes)) { 1938 return false; 1939 } 1940 1941 mTransactionManager.getCurrentTransactionRecord() 1942 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); 1943 mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), 1944 false /* shouldFinishDependent */); 1945 return true; 1946 } 1947 1948 /** 1949 * Returns the rule to launch a placeholder for the activity with the provided component name 1950 * if it is configured in the split config. 1951 */ 1952 @GuardedBy("mLock") getPlaceholderRule(@onNull Activity activity)1953 private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) { 1954 for (EmbeddingRule rule : mSplitRules) { 1955 if (!(rule instanceof SplitPlaceholderRule)) { 1956 continue; 1957 } 1958 SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule; 1959 if (placeholderRule.matchesActivity(activity)) { 1960 return placeholderRule; 1961 } 1962 } 1963 return null; 1964 } 1965 1966 /** 1967 * Notifies listeners about changes to split states if necessary. 1968 */ 1969 @VisibleForTesting 1970 @GuardedBy("mLock") updateCallbackIfNecessary()1971 void updateCallbackIfNecessary() { 1972 if (mEmbeddingCallback == null || !readyToReportToClient()) { 1973 return; 1974 } 1975 final List<SplitInfo> currentSplitStates = getActiveSplitStates(); 1976 if (mLastReportedSplitStates.equals(currentSplitStates)) { 1977 return; 1978 } 1979 mLastReportedSplitStates.clear(); 1980 mLastReportedSplitStates.addAll(currentSplitStates); 1981 mEmbeddingCallback.accept(currentSplitStates); 1982 } 1983 1984 /** 1985 * Returns a list of descriptors for currently active split states. 1986 */ 1987 @GuardedBy("mLock") 1988 @NonNull getActiveSplitStates()1989 private List<SplitInfo> getActiveSplitStates() { 1990 final List<SplitInfo> splitStates = new ArrayList<>(); 1991 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 1992 mTaskContainers.valueAt(i).getSplitStates(splitStates); 1993 } 1994 return splitStates; 1995 } 1996 1997 /** 1998 * Whether we can now report the split states to the client. 1999 */ 2000 @GuardedBy("mLock") readyToReportToClient()2001 private boolean readyToReportToClient() { 2002 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2003 if (mTaskContainers.valueAt(i).isInIntermediateState()) { 2004 // If any Task is in an intermediate state, wait for the server update. 2005 return false; 2006 } 2007 } 2008 return true; 2009 } 2010 2011 /** 2012 * Returns {@code true} if the container is expanded to occupy full task size. 2013 * Returns {@code false} if the container is included in an active split. 2014 */ shouldContainerBeExpanded(@ullable TaskFragmentContainer container)2015 boolean shouldContainerBeExpanded(@Nullable TaskFragmentContainer container) { 2016 if (container == null) { 2017 return false; 2018 } 2019 return getActiveSplitForContainer(container) == null; 2020 } 2021 2022 /** 2023 * Returns a split rule for the provided pair of primary activity and secondary activity intent 2024 * if available. 2025 */ 2026 @GuardedBy("mLock") 2027 @Nullable getSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent)2028 private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, 2029 @NonNull Intent secondaryActivityIntent) { 2030 for (EmbeddingRule rule : mSplitRules) { 2031 if (!(rule instanceof SplitPairRule)) { 2032 continue; 2033 } 2034 SplitPairRule pairRule = (SplitPairRule) rule; 2035 if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) { 2036 return pairRule; 2037 } 2038 } 2039 return null; 2040 } 2041 2042 /** 2043 * Returns a split rule for the provided pair of primary and secondary activities if available. 2044 */ 2045 @GuardedBy("mLock") 2046 @Nullable getSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)2047 private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, 2048 @NonNull Activity secondaryActivity) { 2049 for (EmbeddingRule rule : mSplitRules) { 2050 if (!(rule instanceof SplitPairRule)) { 2051 continue; 2052 } 2053 SplitPairRule pairRule = (SplitPairRule) rule; 2054 final Intent intent = secondaryActivity.getIntent(); 2055 if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity) 2056 && (intent == null 2057 || pairRule.matchesActivityIntentPair(primaryActivity, intent))) { 2058 return pairRule; 2059 } 2060 } 2061 return null; 2062 } 2063 2064 @Nullable 2065 @GuardedBy("mLock") getContainer(@onNull IBinder fragmentToken)2066 TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { 2067 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2068 final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) 2069 .getTaskFragmentContainers(); 2070 for (TaskFragmentContainer container : containers) { 2071 if (container.getTaskFragmentToken().equals(fragmentToken)) { 2072 return container; 2073 } 2074 } 2075 } 2076 return null; 2077 } 2078 2079 @VisibleForTesting 2080 @Nullable 2081 @GuardedBy("mLock") getSplitContainer(@onNull IBinder token)2082 SplitContainer getSplitContainer(@NonNull IBinder token) { 2083 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2084 final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers(); 2085 for (SplitContainer container : containers) { 2086 if (container.getToken().equals(token)) { 2087 return container; 2088 } 2089 } 2090 } 2091 return null; 2092 } 2093 2094 @Nullable 2095 @GuardedBy("mLock") getTaskContainer(int taskId)2096 TaskContainer getTaskContainer(int taskId) { 2097 return mTaskContainers.get(taskId); 2098 } 2099 getHandler()2100 Handler getHandler() { 2101 return mHandler; 2102 } 2103 2104 @GuardedBy("mLock") getTaskId(@onNull Activity activity)2105 int getTaskId(@NonNull Activity activity) { 2106 // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an 2107 // IPC call. 2108 final TaskFragmentContainer container = getContainerWithActivity(activity); 2109 return container != null ? container.getTaskId() : activity.getTaskId(); 2110 } 2111 2112 @Nullable getActivity(@onNull IBinder activityToken)2113 Activity getActivity(@NonNull IBinder activityToken) { 2114 return ActivityThread.currentActivityThread().getActivity(activityToken); 2115 } 2116 2117 @VisibleForTesting getActivityStartMonitor()2118 ActivityStartMonitor getActivityStartMonitor() { 2119 return mActivityStartMonitor; 2120 } 2121 2122 /** 2123 * Gets the token of the TaskFragment that embedded this activity. It is available as soon as 2124 * the activity is created and attached, so it can be used during {@link #onActivityCreated} 2125 * before the server notifies the organizer to avoid racing condition. 2126 */ 2127 @VisibleForTesting 2128 @Nullable getTaskFragmentTokenFromActivityClientRecord(@onNull Activity activity)2129 IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) { 2130 final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread() 2131 .getActivityClient(activity.getActivityToken()); 2132 return record != null ? record.mTaskFragmentToken : null; 2133 } 2134 2135 /** 2136 * Returns {@code true} if an Activity with the provided component name should always be 2137 * expanded to occupy full task bounds. Such activity must not be put in a split. 2138 */ 2139 @VisibleForTesting 2140 @GuardedBy("mLock") shouldExpand(@ullable Activity activity, @Nullable Intent intent)2141 boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { 2142 for (EmbeddingRule rule : mSplitRules) { 2143 if (!(rule instanceof ActivityRule)) { 2144 continue; 2145 } 2146 ActivityRule activityRule = (ActivityRule) rule; 2147 if (!activityRule.shouldAlwaysExpand()) { 2148 continue; 2149 } 2150 if (activity != null && activityRule.matchesActivity(activity)) { 2151 return true; 2152 } else if (intent != null && activityRule.matchesIntent(intent)) { 2153 return true; 2154 } 2155 } 2156 return false; 2157 } 2158 2159 /** 2160 * Checks whether the associated container should be destroyed together with a finishing 2161 * container. There is a case when primary containers for placeholders should be retained 2162 * despite the rule configuration to finish primary with secondary - if they are marked as 2163 * 'sticky' and the placeholder was finished when fully overlapping the primary container. 2164 * @return {@code true} if the associated container should be retained (and not be finished). 2165 */ 2166 // Suppress GuardedBy warning because lint ask to mark this method as 2167 // @GuardedBy(mPresenter.mController.mLock), which is mLock itself 2168 @SuppressWarnings("GuardedBy") 2169 @GuardedBy("mLock") shouldRetainAssociatedContainer(@onNull TaskFragmentContainer finishingContainer, @NonNull TaskFragmentContainer associatedContainer)2170 boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer, 2171 @NonNull TaskFragmentContainer associatedContainer) { 2172 SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, 2173 finishingContainer); 2174 if (splitContainer == null) { 2175 // Containers are not in the same split, no need to retain. 2176 return false; 2177 } 2178 // Find the finish behavior for the associated container 2179 int finishBehavior; 2180 SplitRule splitRule = splitContainer.getSplitRule(); 2181 if (finishingContainer == splitContainer.getPrimaryContainer()) { 2182 finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule); 2183 } else { 2184 finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule); 2185 } 2186 // Decide whether the associated container should be retained based on the current 2187 // presentation mode. 2188 if (shouldShowSplit(splitContainer)) { 2189 return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); 2190 } else { 2191 return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); 2192 } 2193 } 2194 2195 /** 2196 * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer) 2197 */ 2198 @GuardedBy("mLock") shouldRetainAssociatedActivity(@onNull TaskFragmentContainer finishingContainer, @NonNull Activity associatedActivity)2199 boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer, 2200 @NonNull Activity associatedActivity) { 2201 final TaskFragmentContainer associatedContainer = getContainerWithActivity( 2202 associatedActivity); 2203 if (associatedContainer == null) { 2204 return false; 2205 } 2206 2207 return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); 2208 } 2209 2210 private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter { 2211 2212 @Override onActivityPreCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)2213 public void onActivityPreCreated(@NonNull Activity activity, 2214 @Nullable Bundle savedInstanceState) { 2215 if (activity.isChild()) { 2216 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2217 // window will just be a child of the parent Activity window. 2218 return; 2219 } 2220 synchronized (mLock) { 2221 final IBinder activityToken = activity.getActivityToken(); 2222 final IBinder initialTaskFragmentToken = 2223 getTaskFragmentTokenFromActivityClientRecord(activity); 2224 // If the activity is not embedded, then it will not have an initial task fragment 2225 // token so no further action is needed. 2226 if (initialTaskFragmentToken == null) { 2227 return; 2228 } 2229 for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 2230 final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) 2231 .getTaskFragmentContainers(); 2232 for (int j = containers.size() - 1; j >= 0; j--) { 2233 final TaskFragmentContainer container = containers.get(j); 2234 if (!container.hasActivity(activityToken) 2235 && container.getTaskFragmentToken() 2236 .equals(initialTaskFragmentToken)) { 2237 if (ActivityClient.getInstance().isRequestedToLaunchInTaskFragment( 2238 activityToken, initialTaskFragmentToken)) { 2239 container.addPendingAppearedInRequestedTaskFragmentActivity( 2240 activity); 2241 } 2242 2243 // The onTaskFragmentInfoChanged callback containing this activity has 2244 // not reached the client yet, so add the activity to the pending 2245 // appeared activities. 2246 container.addPendingAppearedActivity(activity); 2247 return; 2248 } 2249 } 2250 } 2251 } 2252 } 2253 2254 @Override onActivityPostCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)2255 public void onActivityPostCreated(@NonNull Activity activity, 2256 @Nullable Bundle savedInstanceState) { 2257 if (activity.isChild()) { 2258 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2259 // window will just be a child of the parent Activity window. 2260 return; 2261 } 2262 // Calling after Activity#onCreate is complete to allow the app launch something 2263 // first. In case of a configured placeholder activity we want to make sure 2264 // that we don't launch it if an activity itself already requested something to be 2265 // launched to side. 2266 synchronized (mLock) { 2267 final TransactionRecord transactionRecord = mTransactionManager 2268 .startNewTransaction(); 2269 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); 2270 SplitController.this.onActivityCreated(transactionRecord.getTransaction(), 2271 activity); 2272 // The WCT should be applied and merged to the activity launch transition. 2273 transactionRecord.apply(false /* shouldApplyIndependently */); 2274 } 2275 } 2276 2277 @Override onActivityConfigurationChanged(@onNull Activity activity)2278 public void onActivityConfigurationChanged(@NonNull Activity activity) { 2279 if (activity.isChild()) { 2280 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2281 // window will just be a child of the parent Activity window. 2282 return; 2283 } 2284 synchronized (mLock) { 2285 final TransactionRecord transactionRecord = mTransactionManager 2286 .startNewTransaction(); 2287 SplitController.this.onActivityConfigurationChanged( 2288 transactionRecord.getTransaction(), activity); 2289 // The WCT should be applied and merged to the Task change transition so that the 2290 // placeholder is launched in the same transition. 2291 transactionRecord.apply(false /* shouldApplyIndependently */); 2292 } 2293 } 2294 2295 @Override onActivityPostDestroyed(@onNull Activity activity)2296 public void onActivityPostDestroyed(@NonNull Activity activity) { 2297 if (activity.isChild()) { 2298 // Skip Activity that is child of another Activity (ActivityGroup) because it's 2299 // window will just be a child of the parent Activity window. 2300 return; 2301 } 2302 synchronized (mLock) { 2303 SplitController.this.onActivityDestroyed(activity); 2304 } 2305 } 2306 } 2307 2308 /** Executor that posts on the main application thread. */ 2309 private static class MainThreadExecutor implements Executor { 2310 private final Handler mHandler = new Handler(Looper.getMainLooper()); 2311 2312 @Override execute(@onNull Runnable r)2313 public void execute(@NonNull Runnable r) { 2314 mHandler.post(r); 2315 } 2316 } 2317 2318 /** 2319 * A monitor that intercepts all activity start requests originating in the client process and 2320 * can amend them to target a specific task fragment to form a split. 2321 */ 2322 @VisibleForTesting 2323 class ActivityStartMonitor extends Instrumentation.ActivityMonitor { 2324 @VisibleForTesting 2325 @GuardedBy("mLock") 2326 Intent mCurrentIntent; 2327 2328 @Override onStartActivity(@onNull Context who, @NonNull Intent intent, @NonNull Bundle options)2329 public Instrumentation.ActivityResult onStartActivity(@NonNull Context who, 2330 @NonNull Intent intent, @NonNull Bundle options) { 2331 // TODO(b/232042367): Consolidate the activity create handling so that we can handle 2332 // cross-process the same as normal. 2333 2334 // Early return if the launching taskfragment is already been set. 2335 if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { 2336 synchronized (mLock) { 2337 mCurrentIntent = intent; 2338 } 2339 return super.onStartActivity(who, intent, options); 2340 } 2341 2342 final Activity launchingActivity; 2343 if (who instanceof Activity) { 2344 // We will check if the new activity should be split with the activity that launched 2345 // it. 2346 final Activity activity = (Activity) who; 2347 // For Activity that is child of another Activity (ActivityGroup), treat the parent 2348 // Activity as the launching one because it's window will just be a child of the 2349 // parent Activity window. 2350 launchingActivity = activity.isChild() ? activity.getParent() : activity; 2351 if (isInPictureInPicture(launchingActivity)) { 2352 // We don't embed activity when it is in PIP. 2353 return super.onStartActivity(who, intent, options); 2354 } 2355 } else { 2356 // When the context to start activity is not an Activity context, we will check if 2357 // the new activity should be embedded in the known Task belonging to the organizer 2358 // process. @see #resolveStartActivityIntentFromNonActivityContext 2359 // It is a current security limitation that we can't access the activity info of 2360 // other process even if it is in the same Task. 2361 launchingActivity = null; 2362 } 2363 2364 synchronized (mLock) { 2365 final TransactionRecord transactionRecord = mTransactionManager 2366 .startNewTransaction(); 2367 transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); 2368 final WindowContainerTransaction wct = transactionRecord.getTransaction(); 2369 final TaskFragmentContainer launchedInTaskFragment; 2370 if (launchingActivity != null) { 2371 final int taskId = getTaskId(launchingActivity); 2372 launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, 2373 launchingActivity); 2374 } else { 2375 launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct, 2376 intent); 2377 } 2378 if (launchedInTaskFragment != null) { 2379 // Make sure the WCT is applied immediately instead of being queued so that the 2380 // TaskFragment will be ready before activity attachment. 2381 transactionRecord.apply(false /* shouldApplyIndependently */); 2382 // Amend the request to let the WM know that the activity should be placed in 2383 // the dedicated container. 2384 // TODO(b/229680885): skip override launching TaskFragment token by split-rule 2385 options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, 2386 launchedInTaskFragment.getTaskFragmentToken()); 2387 mCurrentIntent = intent; 2388 } else { 2389 transactionRecord.abort(); 2390 } 2391 } 2392 2393 return super.onStartActivity(who, intent, options); 2394 } 2395 2396 @Override onStartActivityResult(int result, @NonNull Bundle bOptions)2397 public void onStartActivityResult(int result, @NonNull Bundle bOptions) { 2398 super.onStartActivityResult(result, bOptions); 2399 synchronized (mLock) { 2400 if (mCurrentIntent != null && result != START_SUCCESS) { 2401 // Clear the pending appeared intent if the activity was not started 2402 // successfully. 2403 final IBinder token = bOptions.getBinder( 2404 ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN); 2405 if (token != null) { 2406 final TaskFragmentContainer container = getContainer(token); 2407 if (container != null) { 2408 container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); 2409 } 2410 } 2411 } 2412 mCurrentIntent = null; 2413 } 2414 } 2415 } 2416 2417 /** 2418 * Checks if an activity is embedded and its presentation is customized by a 2419 * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds. 2420 */ 2421 @Override isActivityEmbedded(@onNull Activity activity)2422 public boolean isActivityEmbedded(@NonNull Activity activity) { 2423 synchronized (mLock) { 2424 return mPresenter.isActivityEmbedded(activity.getActivityToken()); 2425 } 2426 } 2427 2428 /** 2429 * If the two rules have the same presentation, and the calculated {@link SplitAttributes} 2430 * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same 2431 * {@link SplitContainer} if there is any. 2432 */ canReuseContainer(@onNull SplitRule rule1, @NonNull SplitRule rule2, @NonNull WindowMetrics parentWindowMetrics, @NonNull SplitAttributes calculatedSplitAttributes, @NonNull SplitAttributes containerSplitAttributes)2433 private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2, 2434 @NonNull WindowMetrics parentWindowMetrics, 2435 @NonNull SplitAttributes calculatedSplitAttributes, 2436 @NonNull SplitAttributes containerSplitAttributes) { 2437 if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { 2438 return false; 2439 } 2440 return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2, 2441 parentWindowMetrics) 2442 // Besides rules, we should also check whether the SplitContainer's splitAttributes 2443 // matches the current splitAttributes or not. The splitAttributes may change 2444 // if the app chooses different SplitAttributes calculator function before a new 2445 // activity is started even they match the same splitRule. 2446 && calculatedSplitAttributes.equals(containerSplitAttributes); 2447 } 2448 2449 /** Whether the two rules have the same presentation. */ 2450 @VisibleForTesting areRulesSamePresentation(@onNull SplitPairRule rule1, @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics)2451 static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1, 2452 @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) { 2453 if (rule1.getTag() != null || rule2.getTag() != null) { 2454 // Tag must be unique if it is set. We don't want to reuse the container if the rules 2455 // have different tags because they can have different SplitAttributes later through 2456 // SplitAttributesCalculator. 2457 return Objects.equals(rule1.getTag(), rule2.getTag()); 2458 } 2459 // If both rules don't have tag, compare all SplitRules' properties that may affect their 2460 // SplitAttributes. 2461 // TODO(b/231655482): add util method to do the comparison in SplitPairRule. 2462 return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes()) 2463 && rule1.checkParentMetrics(parentWindowMetrics) 2464 == rule2.checkParentMetrics(parentWindowMetrics) 2465 && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary() 2466 && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary(); 2467 } 2468 2469 /** 2470 * Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given 2471 * rule. 2472 */ isContainerReusableRule(@onNull SplitRule rule)2473 private static boolean isContainerReusableRule(@NonNull SplitRule rule) { 2474 // We don't expect to reuse the placeholder rule. 2475 if (!(rule instanceof SplitPairRule)) { 2476 return false; 2477 } 2478 final SplitPairRule pairRule = (SplitPairRule) rule; 2479 2480 // Not reuse if it needs to destroy the existing. 2481 return !pairRule.shouldClearTop(); 2482 } 2483 isInPictureInPicture(@onNull Activity activity)2484 private static boolean isInPictureInPicture(@NonNull Activity activity) { 2485 return isInPictureInPicture(activity.getResources().getConfiguration()); 2486 } 2487 isInPictureInPicture(@onNull TaskFragmentContainer tf)2488 private static boolean isInPictureInPicture(@NonNull TaskFragmentContainer tf) { 2489 return isInPictureInPicture(tf.getInfo().getConfiguration()); 2490 } 2491 isInPictureInPicture(@ullable Configuration configuration)2492 private static boolean isInPictureInPicture(@Nullable Configuration configuration) { 2493 return configuration != null 2494 && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; 2495 } 2496 } 2497