• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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