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.content.pm.PackageManager.MATCH_ALL; 20 21 import android.app.Activity; 22 import android.app.ActivityThread; 23 import android.app.WindowConfiguration; 24 import android.app.WindowConfiguration.WindowingMode; 25 import android.content.Intent; 26 import android.content.pm.ActivityInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.res.Configuration; 30 import android.graphics.Rect; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.util.LayoutDirection; 34 import android.util.Pair; 35 import android.util.Size; 36 import android.view.View; 37 import android.view.WindowInsets; 38 import android.view.WindowMetrics; 39 import android.window.TaskFragmentAnimationParams; 40 import android.window.TaskFragmentCreationParams; 41 import android.window.WindowContainerTransaction; 42 43 import androidx.annotation.IntDef; 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.window.extensions.core.util.function.Function; 47 import androidx.window.extensions.embedding.SplitAttributes.SplitType; 48 import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType; 49 import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType; 50 import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType; 51 import androidx.window.extensions.embedding.TaskContainer.TaskProperties; 52 import androidx.window.extensions.layout.DisplayFeature; 53 import androidx.window.extensions.layout.FoldingFeature; 54 import androidx.window.extensions.layout.WindowLayoutComponentImpl; 55 import androidx.window.extensions.layout.WindowLayoutInfo; 56 57 import com.android.internal.annotations.VisibleForTesting; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 import java.util.Objects; 62 import java.util.concurrent.Executor; 63 64 /** 65 * Controls the visual presentation of the splits according to the containers formed by 66 * {@link SplitController}. 67 * 68 * Note that all calls into this class must hold the {@link SplitController} internal lock. 69 */ 70 @SuppressWarnings("GuardedBy") 71 class SplitPresenter extends JetpackTaskFragmentOrganizer { 72 @VisibleForTesting 73 static final int POSITION_START = 0; 74 @VisibleForTesting 75 static final int POSITION_END = 1; 76 @VisibleForTesting 77 static final int POSITION_FILL = 2; 78 79 @IntDef(value = { 80 POSITION_START, 81 POSITION_END, 82 POSITION_FILL, 83 }) 84 private @interface Position {} 85 86 private static final int CONTAINER_POSITION_LEFT = 0; 87 private static final int CONTAINER_POSITION_TOP = 1; 88 private static final int CONTAINER_POSITION_RIGHT = 2; 89 private static final int CONTAINER_POSITION_BOTTOM = 3; 90 91 @IntDef(value = { 92 CONTAINER_POSITION_LEFT, 93 CONTAINER_POSITION_TOP, 94 CONTAINER_POSITION_RIGHT, 95 CONTAINER_POSITION_BOTTOM, 96 }) 97 private @interface ContainerPosition {} 98 99 /** 100 * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, 101 * Activity, Activity, Intent)}. 102 * No need to expand the splitContainer because screen is big enough to 103 * {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is 104 * satisfied. 105 */ 106 static final int RESULT_NOT_EXPANDED = 0; 107 /** 108 * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, 109 * Activity, Activity, Intent)}. 110 * The splitContainer should be expanded. It is usually because minimum dimensions is not 111 * satisfied. 112 * @see #shouldShowSplit(SplitAttributes) 113 */ 114 static final int RESULT_EXPANDED = 1; 115 /** 116 * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, 117 * Activity, Activity, Intent)}. 118 * The splitContainer should be expanded, but the client side hasn't received 119 * {@link android.window.TaskFragmentInfo} yet. Fallback to create new expanded SplitContainer 120 * instead. 121 */ 122 static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2; 123 124 /** 125 * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, 126 * Activity, Activity, Intent)} 127 */ 128 @IntDef(value = { 129 RESULT_NOT_EXPANDED, 130 RESULT_EXPANDED, 131 RESULT_EXPAND_FAILED_NO_TF_INFO, 132 }) 133 private @interface ResultCode {} 134 135 @VisibleForTesting 136 static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES = 137 new SplitAttributes.Builder() 138 .setSplitType(new ExpandContainersSplitType()) 139 .build(); 140 141 private final WindowLayoutComponentImpl mWindowLayoutComponent; 142 private final SplitController mController; 143 SplitPresenter(@onNull Executor executor, @NonNull WindowLayoutComponentImpl windowLayoutComponent, @NonNull SplitController controller)144 SplitPresenter(@NonNull Executor executor, 145 @NonNull WindowLayoutComponentImpl windowLayoutComponent, 146 @NonNull SplitController controller) { 147 super(executor, controller); 148 mWindowLayoutComponent = windowLayoutComponent; 149 mController = controller; 150 registerOrganizer(); 151 if (!SplitController.ENABLE_SHELL_TRANSITIONS) { 152 // TODO(b/207070762): cleanup with legacy app transition 153 // Animation will be handled by WM Shell when Shell transition is enabled. 154 overrideSplitAnimation(); 155 } 156 } 157 158 /** 159 * Deletes the specified container and all other associated and dependent containers in the same 160 * transaction. 161 */ cleanupContainer(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, boolean shouldFinishDependent)162 void cleanupContainer(@NonNull WindowContainerTransaction wct, 163 @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { 164 container.finish(shouldFinishDependent, this, wct, mController); 165 // Make sure the containers in the Task is up-to-date. 166 mController.updateContainersInTaskIfVisible(wct, container.getTaskId()); 167 } 168 169 /** 170 * Creates a new split with the primary activity and an empty secondary container. 171 * @return The newly created secondary container. 172 */ 173 @NonNull createNewSplitWithEmptySideContainer( @onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule)174 TaskFragmentContainer createNewSplitWithEmptySideContainer( 175 @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, 176 @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { 177 final TaskProperties taskProperties = getTaskProperties(primaryActivity); 178 final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( 179 primaryActivity, secondaryIntent); 180 final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, 181 minDimensionsPair); 182 final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, 183 splitAttributes); 184 final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, 185 primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */); 186 187 // Create new empty task fragment 188 final int taskId = primaryContainer.getTaskId(); 189 final TaskFragmentContainer secondaryContainer = mController.newContainer( 190 secondaryIntent, primaryActivity, taskId); 191 final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, 192 splitAttributes); 193 final int windowingMode = mController.getTaskContainer(taskId) 194 .getWindowingModeForSplitTaskFragment(secondaryRectBounds); 195 createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), 196 primaryActivity.getActivityToken(), secondaryRectBounds, 197 windowingMode); 198 updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); 199 200 // Set adjacent to each other so that the containers below will be invisible. 201 setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, 202 splitAttributes); 203 204 mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule, 205 splitAttributes); 206 207 return secondaryContainer; 208 } 209 210 /** 211 * Creates a new split container with the two provided activities. 212 * @param primaryActivity An activity that should be in the primary container. If it is not 213 * currently in an existing container, a new one will be created and the 214 * activity will be re-parented to it. 215 * @param secondaryActivity An activity that should be in the secondary container. If it is not 216 * currently in an existing container, or if it is currently in the 217 * same container as the primary activity, a new container will be 218 * created and the activity will be re-parented to it. 219 * @param rule The split rule to be applied to the container. 220 */ createNewSplitContainer(@onNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule)221 void createNewSplitContainer(@NonNull WindowContainerTransaction wct, 222 @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, 223 @NonNull SplitPairRule rule) { 224 final TaskProperties taskProperties = getTaskProperties(primaryActivity); 225 final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, 226 secondaryActivity); 227 final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, 228 minDimensionsPair); 229 final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, 230 splitAttributes); 231 final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, 232 primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */); 233 234 final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, 235 splitAttributes); 236 final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( 237 secondaryActivity); 238 TaskFragmentContainer containerToAvoid = primaryContainer; 239 if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer 240 && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) { 241 // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below 242 // the primary TaskFragment. 243 containerToAvoid = curSecondaryContainer; 244 } 245 final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, 246 secondaryActivity, secondaryRectBounds, splitAttributes, containerToAvoid); 247 248 // Set adjacent to each other so that the containers below will be invisible. 249 setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, 250 splitAttributes); 251 252 mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule, 253 splitAttributes); 254 } 255 256 /** 257 * Creates a new container or resizes an existing container for activity to the provided bounds. 258 * @param activity The activity to be re-parented to the container if necessary. 259 * @param containerToAvoid Re-parent from this container if an activity is already in it. 260 */ prepareContainerForActivity( @onNull WindowContainerTransaction wct, @NonNull Activity activity, @NonNull Rect bounds, @NonNull SplitAttributes splitAttributes, @Nullable TaskFragmentContainer containerToAvoid)261 private TaskFragmentContainer prepareContainerForActivity( 262 @NonNull WindowContainerTransaction wct, @NonNull Activity activity, 263 @NonNull Rect bounds, @NonNull SplitAttributes splitAttributes, 264 @Nullable TaskFragmentContainer containerToAvoid) { 265 TaskFragmentContainer container = mController.getContainerWithActivity(activity); 266 final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); 267 if (container == null || container == containerToAvoid) { 268 container = mController.newContainer(activity, taskId); 269 final int windowingMode = mController.getTaskContainer(taskId) 270 .getWindowingModeForSplitTaskFragment(bounds); 271 final IBinder reparentActivityToken = activity.getActivityToken(); 272 createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken, 273 bounds, windowingMode, reparentActivityToken); 274 wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), 275 reparentActivityToken); 276 } else { 277 resizeTaskFragmentIfRegistered(wct, container, bounds); 278 final int windowingMode = mController.getTaskContainer(taskId) 279 .getWindowingModeForSplitTaskFragment(bounds); 280 updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); 281 } 282 updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes); 283 284 return container; 285 } 286 287 /** 288 * Starts a new activity to the side, creating a new split container. A new container will be 289 * created for the activity that will be started. 290 * @param launchingActivity An activity that should be in the primary container. If it is not 291 * currently in an existing container, a new one will be created and 292 * the activity will be re-parented to it. 293 * @param activityIntent The intent to start the new activity. 294 * @param activityOptions The options to apply to new activity start. 295 * @param rule The split rule to be applied to the container. 296 * @param isPlaceholder Whether the launch is a placeholder. 297 */ startActivityToSide(@onNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, @NonNull SplitAttributes splitAttributes, boolean isPlaceholder)298 void startActivityToSide(@NonNull WindowContainerTransaction wct, 299 @NonNull Activity launchingActivity, @NonNull Intent activityIntent, 300 @Nullable Bundle activityOptions, @NonNull SplitRule rule, 301 @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) { 302 final TaskProperties taskProperties = getTaskProperties(launchingActivity); 303 final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, 304 splitAttributes); 305 final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, 306 splitAttributes); 307 308 TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( 309 launchingActivity); 310 if (primaryContainer == null) { 311 primaryContainer = mController.newContainer(launchingActivity, 312 launchingActivity.getTaskId()); 313 } 314 315 final int taskId = primaryContainer.getTaskId(); 316 final TaskFragmentContainer secondaryContainer = mController.newContainer( 317 null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId, 318 // Pass in the primary container to make sure it is added right above the primary. 319 primaryContainer); 320 final TaskContainer taskContainer = mController.getTaskContainer(taskId); 321 final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( 322 primaryRectBounds); 323 mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, 324 rule, splitAttributes); 325 startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, 326 launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, 327 activityIntent, activityOptions, rule, windowingMode, splitAttributes); 328 if (isPlaceholder) { 329 // When placeholder is launched in split, we should keep the focus on the primary. 330 wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); 331 } 332 } 333 334 /** 335 * Updates the positions of containers in an existing split. 336 * @param splitContainer The split container to be updated. 337 * @param updatedContainer The task fragment that was updated and caused this split update. 338 * @param wct WindowContainerTransaction that this update should be performed with. 339 */ updateSplitContainer(@onNull SplitContainer splitContainer, @NonNull TaskFragmentContainer updatedContainer, @NonNull WindowContainerTransaction wct)340 void updateSplitContainer(@NonNull SplitContainer splitContainer, 341 @NonNull TaskFragmentContainer updatedContainer, 342 @NonNull WindowContainerTransaction wct) { 343 // Getting the parent configuration using the updated container - it will have the recent 344 // value. 345 final SplitRule rule = splitContainer.getSplitRule(); 346 final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); 347 final Activity activity = primaryContainer.getTopNonFinishingActivity(); 348 if (activity == null) { 349 return; 350 } 351 final TaskProperties taskProperties = getTaskProperties(updatedContainer); 352 final SplitAttributes splitAttributes = splitContainer.getSplitAttributes(); 353 final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, 354 splitAttributes); 355 final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, 356 splitAttributes); 357 final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); 358 // Whether the placeholder is becoming side-by-side with the primary from fullscreen. 359 final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() 360 && secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */) 361 && !secondaryRectBounds.isEmpty(); 362 363 // If the task fragments are not registered yet, the positions will be updated after they 364 // are created again. 365 resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); 366 resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); 367 setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, 368 splitAttributes); 369 if (isPlaceholderBecomingSplit) { 370 // When placeholder is shown in split, we should keep the focus on the primary. 371 wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); 372 } 373 final TaskContainer taskContainer = updatedContainer.getTaskContainer(); 374 final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( 375 primaryRectBounds); 376 updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); 377 updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); 378 updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes); 379 updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); 380 } 381 setAdjacentTaskFragments(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes)382 private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, 383 @NonNull TaskFragmentContainer primaryContainer, 384 @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, 385 @NonNull SplitAttributes splitAttributes) { 386 // Clear adjacent TaskFragments if the container is shown in fullscreen, or the 387 // secondaryContainer could not be finished. 388 boolean isStacked = !shouldShowSplit(splitAttributes); 389 if (isStacked) { 390 setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), 391 null /* secondary */, null /* splitRule */); 392 } else { 393 setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), 394 secondaryContainer.getTaskFragmentToken(), splitRule); 395 } 396 setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(), 397 secondaryContainer.getTaskFragmentToken(), splitRule, isStacked); 398 } 399 400 /** 401 * Resizes the task fragment if it was already registered. Skips the operation if the container 402 * creation has not been reported from the server yet. 403 */ 404 // TODO(b/190433398): Handle resize if the fragment hasn't appeared yet. resizeTaskFragmentIfRegistered(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @Nullable Rect bounds)405 private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct, 406 @NonNull TaskFragmentContainer container, 407 @Nullable Rect bounds) { 408 if (container.getInfo() == null) { 409 return; 410 } 411 resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds); 412 } 413 updateTaskFragmentWindowingModeIfRegistered( @onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @WindowingMode int windowingMode)414 private void updateTaskFragmentWindowingModeIfRegistered( 415 @NonNull WindowContainerTransaction wct, 416 @NonNull TaskFragmentContainer container, 417 @WindowingMode int windowingMode) { 418 if (container.getInfo() != null) { 419 updateWindowingMode(wct, container.getTaskFragmentToken(), windowingMode); 420 } 421 } 422 423 @Override createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentCreationParams fragmentOptions)424 void createTaskFragment(@NonNull WindowContainerTransaction wct, 425 @NonNull TaskFragmentCreationParams fragmentOptions) { 426 final TaskFragmentContainer container = mController.getContainer( 427 fragmentOptions.getFragmentToken()); 428 if (container == null) { 429 throw new IllegalStateException( 430 "Creating a task fragment that is not registered with controller."); 431 } 432 433 container.setLastRequestedBounds(fragmentOptions.getInitialBounds()); 434 container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode()); 435 super.createTaskFragment(wct, fragmentOptions); 436 } 437 438 @Override resizeTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds)439 void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, 440 @Nullable Rect bounds) { 441 TaskFragmentContainer container = mController.getContainer(fragmentToken); 442 if (container == null) { 443 throw new IllegalStateException( 444 "Resizing a task fragment that is not registered with controller."); 445 } 446 447 if (container.areLastRequestedBoundsEqual(bounds)) { 448 // Return early if the provided bounds were already requested 449 return; 450 } 451 452 container.setLastRequestedBounds(bounds); 453 super.resizeTaskFragment(wct, fragmentToken, bounds); 454 } 455 456 @Override updateWindowingMode(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @WindowingMode int windowingMode)457 void updateWindowingMode(@NonNull WindowContainerTransaction wct, 458 @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) { 459 final TaskFragmentContainer container = mController.getContainer(fragmentToken); 460 if (container == null) { 461 throw new IllegalStateException("Setting windowing mode for a task fragment that is" 462 + " not registered with controller."); 463 } 464 465 if (container.isLastRequestedWindowingModeEqual(windowingMode)) { 466 // Return early if the windowing mode were already requested 467 return; 468 } 469 470 container.setLastRequestedWindowingMode(windowingMode); 471 super.updateWindowingMode(wct, fragmentToken, windowingMode); 472 } 473 474 @Override updateAnimationParams(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams)475 void updateAnimationParams(@NonNull WindowContainerTransaction wct, 476 @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) { 477 final TaskFragmentContainer container = mController.getContainer(fragmentToken); 478 if (container == null) { 479 throw new IllegalStateException("Setting animation params for a task fragment that is" 480 + " not registered with controller."); 481 } 482 483 if (container.areLastRequestedAnimationParamsEqual(animationParams)) { 484 // Return early if the animation params were already requested 485 return; 486 } 487 488 container.setLastRequestAnimationParams(animationParams); 489 super.updateAnimationParams(wct, fragmentToken, animationParams); 490 } 491 492 /** 493 * Expands the split container if the current split bounds are smaller than the Activity or 494 * Intent that is added to the container. 495 * 496 * @return the {@link ResultCode} based on 497 * {@link #shouldShowSplit(SplitAttributes)} and if 498 * {@link android.window.TaskFragmentInfo} has reported to the client side. 499 */ 500 @ResultCode expandSplitContainerIfNeeded(@onNull WindowContainerTransaction wct, @NonNull SplitContainer splitContainer, @NonNull Activity primaryActivity, @Nullable Activity secondaryActivity, @Nullable Intent secondaryIntent)501 int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct, 502 @NonNull SplitContainer splitContainer, @NonNull Activity primaryActivity, 503 @Nullable Activity secondaryActivity, @Nullable Intent secondaryIntent) { 504 if (secondaryActivity == null && secondaryIntent == null) { 505 throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be" 506 + " non-null."); 507 } 508 final Pair<Size, Size> minDimensionsPair; 509 if (secondaryActivity != null) { 510 minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); 511 } else { 512 minDimensionsPair = getActivityIntentMinDimensionsPair(primaryActivity, 513 secondaryIntent); 514 } 515 // Expand the splitContainer if minimum dimensions are not satisfied. 516 final TaskContainer taskContainer = splitContainer.getTaskContainer(); 517 final SplitAttributes splitAttributes = sanitizeSplitAttributes( 518 taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(), 519 minDimensionsPair); 520 splitContainer.setSplitAttributes(splitAttributes); 521 if (!shouldShowSplit(splitAttributes)) { 522 // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment 523 // bounds. Return failure to create a new SplitContainer which fills task bounds. 524 if (splitContainer.getPrimaryContainer().getInfo() == null 525 || splitContainer.getSecondaryContainer().getInfo() == null) { 526 return RESULT_EXPAND_FAILED_NO_TF_INFO; 527 } 528 final IBinder primaryToken = 529 splitContainer.getPrimaryContainer().getTaskFragmentToken(); 530 final IBinder secondaryToken = 531 splitContainer.getSecondaryContainer().getTaskFragmentToken(); 532 expandTaskFragment(wct, primaryToken); 533 expandTaskFragment(wct, secondaryToken); 534 // Set the companion TaskFragment when the two containers stacked. 535 setCompanionTaskFragment(wct, primaryToken, secondaryToken, 536 splitContainer.getSplitRule(), true /* isStacked */); 537 return RESULT_EXPANDED; 538 } 539 return RESULT_NOT_EXPANDED; 540 } 541 shouldShowSplit(@onNull SplitContainer splitContainer)542 static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) { 543 return shouldShowSplit(splitContainer.getSplitAttributes()); 544 } 545 shouldShowSplit(@onNull SplitAttributes splitAttributes)546 static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) { 547 return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType); 548 } 549 550 @NonNull computeSplitAttributes(@onNull TaskProperties taskProperties, @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair)551 SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties, 552 @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) { 553 final Configuration taskConfiguration = taskProperties.getConfiguration(); 554 final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration); 555 final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator = 556 mController.getSplitAttributesCalculator(); 557 final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes(); 558 final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics); 559 if (calculator == null) { 560 if (!areDefaultConstraintsSatisfied) { 561 return EXPAND_CONTAINERS_ATTRIBUTES; 562 } 563 return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes, 564 minDimensionsPair); 565 } 566 final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent 567 .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), 568 taskConfiguration.windowConfiguration); 569 final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams( 570 taskWindowMetrics, taskConfiguration, windowLayoutInfo, defaultSplitAttributes, 571 areDefaultConstraintsSatisfied, rule.getTag()); 572 final SplitAttributes splitAttributes = calculator.apply(params); 573 return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair); 574 } 575 576 /** 577 * Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't 578 * meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns 579 * the passed {@link SplitAttributes}. 580 */ 581 @NonNull sanitizeSplitAttributes(@onNull TaskProperties taskProperties, @NonNull SplitAttributes splitAttributes, @Nullable Pair<Size, Size> minDimensionsPair)582 private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties, 583 @NonNull SplitAttributes splitAttributes, 584 @Nullable Pair<Size, Size> minDimensionsPair) { 585 if (minDimensionsPair == null) { 586 return splitAttributes; 587 } 588 final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); 589 final Configuration taskConfiguration = taskProperties.getConfiguration(); 590 final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes, 591 foldingFeature); 592 final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes, 593 foldingFeature); 594 if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) 595 || boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) { 596 return EXPAND_CONTAINERS_ATTRIBUTES; 597 } 598 return splitAttributes; 599 } 600 601 @NonNull getActivitiesMinDimensionsPair( @onNull Activity primaryActivity, @NonNull Activity secondaryActivity)602 private static Pair<Size, Size> getActivitiesMinDimensionsPair( 603 @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { 604 return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity)); 605 } 606 607 @NonNull getActivityIntentMinDimensionsPair(@onNull Activity primaryActivity, @NonNull Intent secondaryIntent)608 static Pair<Size, Size> getActivityIntentMinDimensionsPair(@NonNull Activity primaryActivity, 609 @NonNull Intent secondaryIntent) { 610 return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent)); 611 } 612 613 @Nullable getMinDimensions(@ullable Activity activity)614 static Size getMinDimensions(@Nullable Activity activity) { 615 if (activity == null) { 616 return null; 617 } 618 final ActivityInfo.WindowLayout windowLayout = activity.getActivityInfo().windowLayout; 619 if (windowLayout == null) { 620 return null; 621 } 622 return new Size(windowLayout.minWidth, windowLayout.minHeight); 623 } 624 625 // TODO(b/232871351): find a light-weight approach for this check. 626 @Nullable getMinDimensions(@ullable Intent intent)627 static Size getMinDimensions(@Nullable Intent intent) { 628 if (intent == null) { 629 return null; 630 } 631 final PackageManager packageManager = ActivityThread.currentActivityThread() 632 .getApplication().getPackageManager(); 633 final ResolveInfo resolveInfo = packageManager.resolveActivity(intent, 634 PackageManager.ResolveInfoFlags.of(MATCH_ALL)); 635 if (resolveInfo == null) { 636 return null; 637 } 638 final ActivityInfo activityInfo = resolveInfo.activityInfo; 639 if (activityInfo == null) { 640 return null; 641 } 642 final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout; 643 if (windowLayout == null) { 644 return null; 645 } 646 return new Size(windowLayout.minWidth, windowLayout.minHeight); 647 } 648 boundsSmallerThanMinDimensions(@onNull Rect bounds, @Nullable Size minDimensions)649 private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, 650 @Nullable Size minDimensions) { 651 if (minDimensions == null) { 652 return false; 653 } 654 return bounds.width() < minDimensions.getWidth() 655 || bounds.height() < minDimensions.getHeight(); 656 } 657 658 @VisibleForTesting 659 @NonNull getBoundsForPosition(@osition int position, @NonNull TaskProperties taskProperties, @NonNull SplitAttributes splitAttributes)660 Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties, 661 @NonNull SplitAttributes splitAttributes) { 662 final Configuration taskConfiguration = taskProperties.getConfiguration(); 663 final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); 664 if (!shouldShowSplit(splitAttributes)) { 665 return new Rect(); 666 } 667 switch (position) { 668 case POSITION_START: 669 return getPrimaryBounds(taskConfiguration, splitAttributes, foldingFeature); 670 case POSITION_END: 671 return getSecondaryBounds(taskConfiguration, splitAttributes, foldingFeature); 672 case POSITION_FILL: 673 default: 674 return new Rect(); 675 } 676 } 677 678 @NonNull getPrimaryBounds(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature)679 private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration, 680 @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { 681 final SplitAttributes computedSplitAttributes = updateSplitAttributesType(splitAttributes, 682 computeSplitType(splitAttributes, taskConfiguration, foldingFeature)); 683 if (!shouldShowSplit(computedSplitAttributes)) { 684 return new Rect(); 685 } 686 switch (computedSplitAttributes.getLayoutDirection()) { 687 case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: { 688 return getLeftContainerBounds(taskConfiguration, computedSplitAttributes, 689 foldingFeature); 690 } 691 case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: { 692 return getRightContainerBounds(taskConfiguration, computedSplitAttributes, 693 foldingFeature); 694 } 695 case SplitAttributes.LayoutDirection.LOCALE: { 696 final boolean isLtr = taskConfiguration.getLayoutDirection() 697 == View.LAYOUT_DIRECTION_LTR; 698 return isLtr 699 ? getLeftContainerBounds(taskConfiguration, computedSplitAttributes, 700 foldingFeature) 701 : getRightContainerBounds(taskConfiguration, computedSplitAttributes, 702 foldingFeature); 703 } 704 case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: { 705 return getTopContainerBounds(taskConfiguration, computedSplitAttributes, 706 foldingFeature); 707 } 708 case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: { 709 return getBottomContainerBounds(taskConfiguration, computedSplitAttributes, 710 foldingFeature); 711 } 712 default: 713 throw new IllegalArgumentException("Unknown layout direction:" 714 + computedSplitAttributes.getLayoutDirection()); 715 } 716 } 717 718 @NonNull getSecondaryBounds(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature)719 private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration, 720 @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { 721 final SplitAttributes computedSplitAttributes = updateSplitAttributesType(splitAttributes, 722 computeSplitType(splitAttributes, taskConfiguration, foldingFeature)); 723 if (!shouldShowSplit(computedSplitAttributes)) { 724 return new Rect(); 725 } 726 switch (computedSplitAttributes.getLayoutDirection()) { 727 case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: { 728 return getRightContainerBounds(taskConfiguration, computedSplitAttributes, 729 foldingFeature); 730 } 731 case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: { 732 return getLeftContainerBounds(taskConfiguration, computedSplitAttributes, 733 foldingFeature); 734 } 735 case SplitAttributes.LayoutDirection.LOCALE: { 736 final boolean isLtr = taskConfiguration.getLayoutDirection() 737 == View.LAYOUT_DIRECTION_LTR; 738 return isLtr 739 ? getRightContainerBounds(taskConfiguration, computedSplitAttributes, 740 foldingFeature) 741 : getLeftContainerBounds(taskConfiguration, computedSplitAttributes, 742 foldingFeature); 743 } 744 case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: { 745 return getBottomContainerBounds(taskConfiguration, computedSplitAttributes, 746 foldingFeature); 747 } 748 case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: { 749 return getTopContainerBounds(taskConfiguration, computedSplitAttributes, 750 foldingFeature); 751 } 752 default: 753 throw new IllegalArgumentException("Unknown layout direction:" 754 + splitAttributes.getLayoutDirection()); 755 } 756 } 757 758 /** 759 * Returns the {@link SplitAttributes} that update the {@link SplitType} to 760 * {@code splitTypeToUpdate}. 761 */ updateSplitAttributesType( @onNull SplitAttributes splitAttributes, @NonNull SplitType splitTypeToUpdate)762 private static SplitAttributes updateSplitAttributesType( 763 @NonNull SplitAttributes splitAttributes, @NonNull SplitType splitTypeToUpdate) { 764 return new SplitAttributes.Builder() 765 .setSplitType(splitTypeToUpdate) 766 .setLayoutDirection(splitAttributes.getLayoutDirection()) 767 .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor()) 768 .build(); 769 } 770 771 @NonNull getLeftContainerBounds(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature)772 private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration, 773 @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { 774 final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, 775 CONTAINER_POSITION_LEFT, foldingFeature); 776 final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); 777 return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom); 778 } 779 780 @NonNull getRightContainerBounds(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature)781 private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration, 782 @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { 783 final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, 784 CONTAINER_POSITION_RIGHT, foldingFeature); 785 final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); 786 return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom); 787 } 788 789 @NonNull getTopContainerBounds(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature)790 private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration, 791 @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { 792 final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, 793 CONTAINER_POSITION_TOP, foldingFeature); 794 final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); 795 return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom); 796 } 797 798 @NonNull getBottomContainerBounds(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature)799 private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration, 800 @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { 801 final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, 802 CONTAINER_POSITION_BOTTOM, foldingFeature); 803 final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); 804 return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom); 805 } 806 807 /** 808 * Computes the boundary position between the primary and the secondary containers for the given 809 * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states. 810 * <ol> 811 * <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom 812 * container, which is {@link Rect#bottom} of the top container bounds.</li> 813 * <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top 814 * container, which is {@link Rect#top} of the bottom container bounds.</li> 815 * <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right 816 * container, which is {@link Rect#right} of the left container bounds.</li> 817 * <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom 818 * container, which is {@link Rect#left} of the right container bounds.</li> 819 * </ol> 820 * 821 * @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature) 822 * @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature) 823 * @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature) 824 * @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature) 825 */ computeBoundaryBetweenContainers(@onNull Configuration taskConfiguration, @NonNull SplitAttributes splitAttributes, @ContainerPosition int position, @Nullable FoldingFeature foldingFeature)826 private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration, 827 @NonNull SplitAttributes splitAttributes, @ContainerPosition int position, 828 @Nullable FoldingFeature foldingFeature) { 829 final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); 830 final int startPoint = shouldSplitHorizontally(splitAttributes) 831 ? parentBounds.top 832 : parentBounds.left; 833 final int dimen = shouldSplitHorizontally(splitAttributes) 834 ? parentBounds.height() 835 : parentBounds.width(); 836 final SplitType splitType = splitAttributes.getSplitType(); 837 if (splitType instanceof RatioSplitType) { 838 final RatioSplitType splitRatio = (RatioSplitType) splitType; 839 return (int) (startPoint + dimen * splitRatio.getRatio()); 840 } 841 // At this point, SplitType must be a HingeSplitType and foldingFeature must be 842 // non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier. 843 Objects.requireNonNull(foldingFeature); 844 if (!(splitType instanceof HingeSplitType)) { 845 throw new IllegalArgumentException("Unknown splitType:" + splitType); 846 } 847 final Rect hingeArea = foldingFeature.getBounds(); 848 switch (position) { 849 case CONTAINER_POSITION_LEFT: 850 return hingeArea.left; 851 case CONTAINER_POSITION_TOP: 852 return hingeArea.top; 853 case CONTAINER_POSITION_RIGHT: 854 return hingeArea.right; 855 case CONTAINER_POSITION_BOTTOM: 856 return hingeArea.bottom; 857 default: 858 throw new IllegalArgumentException("Unknown position:" + position); 859 } 860 } 861 862 @Nullable 863 @VisibleForTesting getFoldingFeature(@onNull TaskProperties taskProperties)864 FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) { 865 final int displayId = taskProperties.getDisplayId(); 866 final WindowConfiguration windowConfiguration = taskProperties.getConfiguration() 867 .windowConfiguration; 868 final WindowLayoutInfo info = mWindowLayoutComponent 869 .getCurrentWindowLayoutInfo(displayId, windowConfiguration); 870 final List<DisplayFeature> displayFeatures = info.getDisplayFeatures(); 871 if (displayFeatures.isEmpty()) { 872 return null; 873 } 874 final List<FoldingFeature> foldingFeatures = new ArrayList<>(); 875 for (DisplayFeature displayFeature : displayFeatures) { 876 if (displayFeature instanceof FoldingFeature) { 877 foldingFeatures.add((FoldingFeature) displayFeature); 878 } 879 } 880 // TODO(b/240219484): Support device with multiple hinges. 881 if (foldingFeatures.size() != 1) { 882 return null; 883 } 884 return foldingFeatures.get(0); 885 } 886 887 /** 888 * Indicates that this {@link SplitAttributes} splits the task horizontally. Returns 889 * {@code false} if this {@link SplitAttributes} splits the task vertically. 890 */ shouldSplitHorizontally(SplitAttributes splitAttributes)891 private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) { 892 switch (splitAttributes.getLayoutDirection()) { 893 case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: 894 case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: 895 return true; 896 default: 897 return false; 898 } 899 } 900 901 /** 902 * Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and 903 * window state. 904 * If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed 905 * the ratio if the computed {@link SplitAttributes#getLayoutDirection} is 906 * {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or 907 * {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier. 908 * If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks 909 * the current device and window states to determine whether the split container should split 910 * by hinge or use {@link HingeSplitType#getFallbackSplitType}. 911 */ computeSplitType(@onNull SplitAttributes splitAttributes, @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature)912 private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes, 913 @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) { 914 final int layoutDirection = splitAttributes.getLayoutDirection(); 915 final SplitType splitType = splitAttributes.getSplitType(); 916 if (splitType instanceof ExpandContainersSplitType) { 917 return splitType; 918 } else if (splitType instanceof RatioSplitType) { 919 final RatioSplitType splitRatio = (RatioSplitType) splitType; 920 // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary 921 // computation have the same direction, which is from (top, left) to (bottom, right). 922 final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio()); 923 switch (layoutDirection) { 924 case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: 925 case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: 926 return splitType; 927 case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: 928 case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: 929 return reversedSplitType; 930 case LayoutDirection.LOCALE: { 931 boolean isLtr = taskConfiguration.getLayoutDirection() 932 == View.LAYOUT_DIRECTION_LTR; 933 return isLtr ? splitType : reversedSplitType; 934 } 935 } 936 } else if (splitType instanceof HingeSplitType) { 937 final HingeSplitType hinge = (HingeSplitType) splitType; 938 @WindowingMode 939 final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode(); 940 return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode) 941 ? hinge : hinge.getFallbackSplitType(); 942 } 943 throw new IllegalArgumentException("Unknown SplitType:" + splitType); 944 } 945 shouldSplitByHinge(@onNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode)946 private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes, 947 @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) { 948 // Only HingeSplitType may split the task bounds by hinge. 949 if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) { 950 return false; 951 } 952 // Device is not foldable, so there's no hinge to match. 953 if (foldingFeature == null) { 954 return false; 955 } 956 // The task is in multi-window mode. Match hinge doesn't make sense because current task 957 // bounds may not fit display bounds. 958 if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) { 959 return false; 960 } 961 // Return true if how the split attributes split the task bounds matches the orientation of 962 // folding area orientation. 963 return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature); 964 } 965 isFoldingAreaHorizontal(@onNull FoldingFeature foldingFeature)966 private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) { 967 final Rect bounds = foldingFeature.getBounds(); 968 return bounds.width() > bounds.height(); 969 } 970 971 @NonNull getTaskProperties(@onNull TaskFragmentContainer container)972 static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) { 973 return container.getTaskContainer().getTaskProperties(); 974 } 975 976 @NonNull getTaskProperties(@onNull Activity activity)977 TaskProperties getTaskProperties(@NonNull Activity activity) { 978 final TaskContainer taskContainer = mController.getTaskContainer( 979 mController.getTaskId(activity)); 980 if (taskContainer != null) { 981 return taskContainer.getTaskProperties(); 982 } 983 return TaskProperties.getTaskPropertiesFromActivity(activity); 984 } 985 986 @NonNull getTaskWindowMetrics(@onNull Activity activity)987 WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) { 988 return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration()); 989 } 990 991 @NonNull getTaskWindowMetrics(@onNull Configuration taskConfiguration)992 private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { 993 final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); 994 // TODO(b/190433398): Supply correct insets. 995 return new WindowMetrics(taskBounds, WindowInsets.CONSUMED); 996 } 997 } 998