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 android.app.Activity;
20 import android.os.Binder;
21 import android.os.IBinder;
22 import android.util.Pair;
23 import android.util.Size;
24 import android.window.TaskFragmentParentInfo;
25 import android.window.WindowContainerTransaction;
26 
27 import androidx.annotation.NonNull;
28 import androidx.window.extensions.core.util.function.Function;
29 
30 /**
31  * Client-side descriptor of a split that holds two containers.
32  */
33 class SplitContainer {
34     @NonNull
35     private TaskFragmentContainer mPrimaryContainer;
36     @NonNull
37     private final TaskFragmentContainer mSecondaryContainer;
38     @NonNull
39     private final SplitRule mSplitRule;
40     /** @see SplitContainer#getCurrentSplitAttributes() */
41     @NonNull
42     private SplitAttributes mCurrentSplitAttributes;
43     /** @see SplitContainer#getDefaultSplitAttributes() */
44     @NonNull
45     private SplitAttributes mDefaultSplitAttributes;
46     @NonNull
47     private final IBinder mToken;
48 
49     /**
50      * Whether the selection of which container is primary can be changed at runtime. Runtime
51      * updates is currently possible only for {@link SplitPinContainer}
52      *
53      * @see SplitPinContainer
54      */
55     private final boolean mIsPrimaryContainerMutable;
56 
SplitContainer(@onNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes)57     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
58             @NonNull Activity primaryActivity,
59             @NonNull TaskFragmentContainer secondaryContainer,
60             @NonNull SplitRule splitRule,
61             @NonNull SplitAttributes splitAttributes) {
62         this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes,
63                 false /* isPrimaryContainerMutable */);
64     }
65 
SplitContainer(@onNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable)66     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
67             @NonNull Activity primaryActivity,
68             @NonNull TaskFragmentContainer secondaryContainer,
69             @NonNull SplitRule splitRule,
70             @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
71         mPrimaryContainer = primaryContainer;
72         mSecondaryContainer = secondaryContainer;
73         mSplitRule = splitRule;
74         mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
75         mCurrentSplitAttributes = splitAttributes;
76         mToken = new Binder("SplitContainer");
77         mIsPrimaryContainerMutable = isPrimaryContainerMutable;
78 
79         if (shouldFinishPrimaryWithSecondary(splitRule)) {
80             if (mPrimaryContainer.getRunningActivityCount() == 1
81                     && mPrimaryContainer.hasActivity(primaryActivity.getActivityToken())) {
82                 mSecondaryContainer.addContainerToFinishOnExit(mPrimaryContainer);
83             } else {
84                 // Only adding the activity to be finished vs. the entire TaskFragment while
85                 // the secondary container exits because there are other unrelated activities in the
86                 // primary TaskFragment.
87                 mSecondaryContainer.addActivityToFinishOnExit(primaryActivity);
88             }
89         }
90         if (shouldFinishSecondaryWithPrimary(splitRule)) {
91             mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer);
92         }
93     }
94 
setPrimaryContainer(@onNull TaskFragmentContainer primaryContainer)95     void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
96         if (!mIsPrimaryContainerMutable) {
97             throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
98         }
99         mPrimaryContainer = primaryContainer;
100     }
101 
102     @NonNull
getPrimaryContainer()103     TaskFragmentContainer getPrimaryContainer() {
104         return mPrimaryContainer;
105     }
106 
107     @NonNull
getSecondaryContainer()108     TaskFragmentContainer getSecondaryContainer() {
109         return mSecondaryContainer;
110     }
111 
112     @NonNull
getSplitRule()113     SplitRule getSplitRule() {
114         return mSplitRule;
115     }
116 
117     /**
118      * Returns the current {@link SplitAttributes} this {@code SplitContainer} is showing.
119      * <p>
120      * If the {@code SplitAttributes} calculator function is not set by
121      * {@link SplitController#setSplitAttributesCalculator(Function)}, the current
122      * {@code SplitAttributes} is either to expand the containers if the size constraints of
123      * {@link #getSplitRule()} are not satisfied,
124      * or the {@link #getDefaultSplitAttributes()}, otherwise.
125      * </p><p>
126      * If the {@code SplitAttributes} calculator function is set, the current
127      * {@code SplitAttributes} will be customized by the function, which can be any
128      * {@code SplitAttributes}.
129      * </p>
130      *
131      * @see SplitAttributes.SplitType.ExpandContainersSplitType
132      */
133     @NonNull
getCurrentSplitAttributes()134     SplitAttributes getCurrentSplitAttributes() {
135         return mCurrentSplitAttributes;
136     }
137 
138     /**
139      * Returns the default {@link SplitAttributes} when the parent task container bounds satisfy
140      * {@link #getSplitRule()} constraints.
141      * <p>
142      * The value is usually from {@link SplitRule#getDefaultSplitAttributes} unless it is overridden
143      * by {@link SplitController#updateSplitAttributes(IBinder, SplitAttributes)}.
144      */
145     @NonNull
getDefaultSplitAttributes()146     SplitAttributes getDefaultSplitAttributes() {
147         return mDefaultSplitAttributes;
148     }
149 
150     @NonNull
getToken()151     IBinder getToken() {
152         return mToken;
153     }
154 
155     /**
156      * Updates the {@link SplitAttributes} to this container.
157      * It is usually used when there's a folding state change or
158      * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction,
159      * int, TaskFragmentParentInfo)}.
160      */
updateCurrentSplitAttributes(@onNull SplitAttributes splitAttributes)161     void updateCurrentSplitAttributes(@NonNull SplitAttributes splitAttributes) {
162         mCurrentSplitAttributes = splitAttributes;
163     }
164 
165     /**
166      * Overrides the default {@link SplitAttributes} to this container, which may be different
167      * from {@link SplitRule#getDefaultSplitAttributes}.
168      */
updateDefaultSplitAttributes(@onNull SplitAttributes splitAttributes)169     void updateDefaultSplitAttributes(@NonNull SplitAttributes splitAttributes) {
170         mDefaultSplitAttributes = splitAttributes;
171     }
172 
173     @NonNull
getTaskContainer()174     TaskContainer getTaskContainer() {
175         return getPrimaryContainer().getTaskContainer();
176     }
177 
178     /** Returns the minimum dimension pair of primary container and secondary container. */
179     @NonNull
getMinDimensionsPair()180     Pair<Size, Size> getMinDimensionsPair() {
181         return new Pair<>(mPrimaryContainer.getMinDimensions(),
182                 mSecondaryContainer.getMinDimensions());
183     }
184 
isPlaceholderContainer()185     boolean isPlaceholderContainer() {
186         return (mSplitRule instanceof SplitPlaceholderRule);
187     }
188 
189     @NonNull
toSplitInfo()190     SplitInfo toSplitInfo() {
191         return new SplitInfo(mPrimaryContainer.toActivityStack(),
192                 mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
193     }
194 
shouldFinishPrimaryWithSecondary(@onNull SplitRule splitRule)195     static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
196         final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
197         final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
198                 && ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary()
199                 != SplitRule.FINISH_NEVER;
200         return shouldFinishPrimaryWithSecondary || isPlaceholderContainer;
201     }
202 
shouldFinishSecondaryWithPrimary(@onNull SplitRule splitRule)203     static boolean shouldFinishSecondaryWithPrimary(@NonNull SplitRule splitRule) {
204         final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
205         final boolean shouldFinishSecondaryWithPrimary = (splitRule instanceof SplitPairRule)
206                 && ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary()
207                 != SplitRule.FINISH_NEVER;
208         return shouldFinishSecondaryWithPrimary || isPlaceholderContainer;
209     }
210 
shouldFinishAssociatedContainerWhenStacked(int finishBehavior)211     static boolean shouldFinishAssociatedContainerWhenStacked(int finishBehavior) {
212         return finishBehavior == SplitRule.FINISH_ALWAYS;
213     }
214 
shouldFinishAssociatedContainerWhenAdjacent(int finishBehavior)215     static boolean shouldFinishAssociatedContainerWhenAdjacent(int finishBehavior) {
216         return finishBehavior == SplitRule.FINISH_ALWAYS
217                 || finishBehavior == SplitRule.FINISH_ADJACENT;
218     }
219 
getFinishPrimaryWithSecondaryBehavior(@onNull SplitRule splitRule)220     static int getFinishPrimaryWithSecondaryBehavior(@NonNull SplitRule splitRule) {
221         if (splitRule instanceof SplitPlaceholderRule) {
222             return ((SplitPlaceholderRule) splitRule).getFinishPrimaryWithSecondary();
223         }
224         if (splitRule instanceof SplitPairRule) {
225             return ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary();
226         }
227         return SplitRule.FINISH_NEVER;
228     }
229 
getFinishSecondaryWithPrimaryBehavior(@onNull SplitRule splitRule)230     static int getFinishSecondaryWithPrimaryBehavior(@NonNull SplitRule splitRule) {
231         if (splitRule instanceof SplitPlaceholderRule) {
232             return SplitRule.FINISH_ALWAYS;
233         }
234         if (splitRule instanceof SplitPairRule) {
235             return ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary();
236         }
237         return SplitRule.FINISH_NEVER;
238     }
239 
isStickyPlaceholderRule(@onNull SplitRule splitRule)240     static boolean isStickyPlaceholderRule(@NonNull SplitRule splitRule) {
241         if (!(splitRule instanceof SplitPlaceholderRule)) {
242             return false;
243         }
244         return ((SplitPlaceholderRule) splitRule).isSticky();
245     }
246 
247     @Override
toString()248     public String toString() {
249         return "SplitContainer{"
250                 + " primaryContainer=" + mPrimaryContainer
251                 + ", secondaryContainer=" + mSecondaryContainer
252                 + ", splitRule=" + mSplitRule
253                 + ", currentSplitAttributes" + mCurrentSplitAttributes
254                 + ", defaultSplitAttributes" + mDefaultSplitAttributes
255                 + "}";
256     }
257 }
258