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