• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 android.server.wm.jetpack.embedding;
18 
19 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.DEFAULT_SPLIT_ATTRS;
20 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.EXPAND_SPLIT_ATTRS;
21 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.HINGE_SPLIT_ATTRS;
22 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.assertValidSplit;
23 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createSplitPairRuleBuilder;
24 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifySplitAttributes;
25 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertNotVisible;
26 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumedAndFillsTask;
27 import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
28 
29 import android.app.Activity;
30 import android.content.Intent;
31 import android.platform.test.annotations.Presubmit;
32 import android.server.wm.jetpack.utils.TestActivity;
33 import android.server.wm.jetpack.utils.TestActivityWithId;
34 import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
35 import android.util.Pair;
36 import android.util.Size;
37 
38 import androidx.annotation.NonNull;
39 import androidx.test.ext.junit.runners.AndroidJUnit4;
40 import androidx.window.extensions.embedding.SplitAttributes;
41 import androidx.window.extensions.embedding.SplitAttributes.LayoutDirection;
42 import androidx.window.extensions.embedding.SplitAttributes.SplitType;
43 import androidx.window.extensions.embedding.SplitPairRule;
44 
45 import com.android.compatibility.common.util.ApiTest;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.Collections;
51 import java.util.Set;
52 
53 /**
54  * Tests for the {@link androidx.window.extensions} implementation provided on the device (and only
55  * if one is available) for the Activity Embedding functionality. Specifically tests activity
56  * split bounds.
57  *
58  * Build/Install/Run:
59  *     atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingBoundsTests
60  */
61 @ApiTest(apis = "androidx.window.extensions.embedding.SplitPairRule#getDefaultSplitAttributes")
62 @Presubmit
63 @RunWith(AndroidJUnit4.class)
64 public class ActivityEmbeddingBoundsTests extends ActivityEmbeddingTestBase {
65     public static SplitType UNEVEN_CONTAINERS_DEFAULT_SPLIT_TYPE =
66             new SplitType.RatioSplitType(0.7f);
67 
68     /**
69      * Tests that when two activities are in a split and the parent bounds shrink such that
70      * they can no longer support split activities, then the activities become stacked.
71      */
72     @ApiTest(apis = {"androidx.window.extensions.embedding.SplitRule#checkParentMetrics"})
73     @Test
testParentWindowMetricsPredicate()74     public void testParentWindowMetricsPredicate() {
75         // Launch primary activity
76         final Activity primaryActivity = startFullScreenActivityNewTask(
77                 TestConfigChangeHandlingActivity.class);
78 
79         // Set split pair rule such that if the parent bounds is any smaller than it is now, then
80         // the parent cannot support a split.
81         final int originalTaskWidth = getTaskWidth();
82         final int originalTaskHeight = getTaskHeight();
83         final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
84                 activityActivityPair -> true /* activityPairPredicate */,
85                 activityIntentPair -> true /* activityIntentPredicate */,
86                 parentWindowMetrics -> parentWindowMetrics.getBounds().width() >= originalTaskWidth
87                         && parentWindowMetrics.getBounds().height() >= originalTaskHeight)
88                 .setDefaultSplitAttributes(DEFAULT_SPLIT_ATTRS).build();
89         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
90 
91         // Launch the secondary activity
92         final String secondaryActivityId = "secondaryActivityId";
93         final TestActivity secondaryActivity = (TestActivity) startActivityAndVerifySplitAttributes(
94                 primaryActivity, TestActivityWithId.class, splitPairRule, secondaryActivityId,
95                 mSplitInfoConsumer);
96 
97         // Resize the display multiple times to verify that the activities are correctly split or
98         // stacked depending on the parent bounds. Resizing multiple times simulates a foldable
99         // display is that folded and unfolded multiple times while running the same app.
100         final int numTimesToResize = 2;
101         final Size originalDisplaySize = mReportedDisplayMetrics.getSize();
102         for (int i = 0; i < numTimesToResize; i++) {
103             // Shrink the display by 10% to make the activities stacked
104             mReportedDisplayMetrics.setSize(new Size((int) (originalDisplaySize.getWidth() * 0.9),
105                     (int) (originalDisplaySize.getHeight() * 0.9)));
106             waitAndAssertResumedAndFillsTask(secondaryActivity);
107             waitAndAssertNotVisible(primaryActivity);
108 
109             // Return the display to its original size and verify that the activities are split
110             mReportedDisplayMetrics.setSize(originalDisplaySize);
111             mInstrumentation.waitForIdleSync();
112             assertValidSplit(primaryActivity, secondaryActivity, splitPairRule);
113         }
114     }
115 
116     /**
117      * Tests that the activity bounds for activities in a split match the LTR layout direction
118      * provided in the {@link SplitPairRule}.
119      */
120     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes"
121             + ".LayoutDirection#LEFT_TO_RIGHT")
122     @Test
testLayoutDirection_LeftToRight()123     public void testLayoutDirection_LeftToRight() {
124         // Create a split pair rule with layout direction LEFT_TO_RIGHT and a split ratio that
125         // results in uneven bounds between the primary and secondary containers.
126         final SplitPairRule splitPairRule = createUnevenWidthSplitPairRule(
127                 LayoutDirection.LEFT_TO_RIGHT);
128         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
129 
130         // Start activities in a split and verify that the layout direction is LEFT_TO_RIGHT,
131         // which is checked in {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
132         Activity primaryActivity = startFullScreenActivityNewTask(
133                 TestConfigChangeHandlingActivity.class);
134         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
135                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
136     }
137 
138     /**
139      * Tests that the activity bounds for activities in a split match the RTL layout direction
140      * provided in the {@link SplitPairRule}.
141      */
142     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes"
143             + ".LayoutDirection#RIGHT_TO_LEFT")
144     @Test
testLayoutDirection_RightToLeft()145     public void testLayoutDirection_RightToLeft() {
146         // Create a split pair rule with layout direction RIGHT_TO_LEFT and a split ratio that
147         // results in uneven bounds between the primary and secondary containers.
148         final SplitPairRule splitPairRule = createUnevenWidthSplitPairRule(
149                 LayoutDirection.RIGHT_TO_LEFT);
150         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
151 
152         // Start activities in a split and verify that the layout direction is RIGHT_TO_LEFT,
153         // which is checked in {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
154         Activity primaryActivity = startFullScreenActivityNewTask(
155                 TestConfigChangeHandlingActivity.class);
156         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
157                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
158     }
159 
160     /**
161      * Tests that the activity bounds for activities in a split match the Locale layout direction
162      * provided in the {@link SplitPairRule}.
163      */
164     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes"
165             + ".LayoutDirection#LOCALE")
166     @Test
testLayoutDirection_Locale()167     public void testLayoutDirection_Locale() {
168         // Create a split pair rule with layout direction LOCALE and a split ratio that results in
169         // uneven bounds between the primary and secondary containers.
170         final SplitPairRule splitPairRule = createUnevenWidthSplitPairRule(LayoutDirection.LOCALE);
171         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
172 
173         // Start activities in a split and verify that the layout direction is the device locale,
174         // which is checked in {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
175         Activity primaryActivity = startFullScreenActivityNewTask(
176                 TestConfigChangeHandlingActivity.class);
177         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
178                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
179     }
180 
181     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes"
182             + ".LayoutDirection#TOP_TO_BOTTOM")
183     @Test
testLayoutDirection_TopToBottom()184     public void testLayoutDirection_TopToBottom() {
185         // Create a split pair rule with layout direction TOP_TO_BOTTOM and a split ratio that
186         // results in uneven bounds between the primary and secondary containers.
187         final SplitPairRule splitPairRule = createUnevenWidthSplitPairRule(
188                 LayoutDirection.TOP_TO_BOTTOM);
189         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
190 
191         // Start activities in a split and verify that the layout direction is TOP_TO_BOTTOM,
192         // which is checked in {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
193         Activity primaryActivity = startFullScreenActivityNewTask(
194                 TestConfigChangeHandlingActivity.class);
195         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
196                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
197     }
198 
199     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes"
200             + ".LayoutDirection#BOTTOM_TO_TOP")
201     @Test
testLayoutDirection_BottomToTop()202     public void testLayoutDirection_BottomToTop() {
203         // Create a split pair rule with layout direction BOTTOM_TO_TOP and a split ratio that
204         // results in uneven bounds between the primary and secondary containers.
205         final SplitPairRule splitPairRule = createUnevenWidthSplitPairRule(
206                 LayoutDirection.TOP_TO_BOTTOM);
207         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
208 
209         // Start activities in a split and verify that the layout direction is BOTTOM_TO_TOP,
210         // which is checked in {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
211         Activity primaryActivity = startFullScreenActivityNewTask(
212                 TestConfigChangeHandlingActivity.class);
213         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
214                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
215     }
216 
217     /**
218      * Tests that when two activities enter a split, then their split ratio matches what is in their
219      * {@link SplitPairRule}, and is not assumed to be 0.5 or match the split ratio of the previous
220      * top-most activity split.
221      */
222     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes"
223             + ".SplitType.RatioSplitType#getRatio")
224     @Test
testSplitRatio()225     public void testSplitRatio() {
226         final String activityAId = "activityA";
227         final String activityBId = "activityB";
228         final String activityCId = "activityC";
229         final SplitType activityABSplitRatio = new SplitType.RatioSplitType(0.37f);
230         final SplitType activityBCSplitRatio = new SplitType.RatioSplitType(0.85f);
231 
232         // Create a split rule for activity A and activity B where the split ratio is 0.37.
233         final SplitPairRule splitPairRuleAB = createSplitPairRuleBuilder(
234                 activityActivityPair -> false /* activityPairPredicate */,
235                 activityIntentPair -> matchesActivityIntentPair(activityIntentPair, activityAId,
236                         activityBId) /* activityIntentPredicate */,
237                 parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
238                 .setDefaultSplitAttributes(new SplitAttributes.Builder().setSplitType(
239                         activityABSplitRatio).build())
240                 .build();
241 
242         // Create a split rule for activity B and activity C where the split ratio is 0.65.
243         final SplitPairRule splitPairRuleBC = createSplitPairRuleBuilder(
244                 activityActivityPair -> false /* activityPairPredicate */,
245                 activityIntentPair -> matchesActivityIntentPair(activityIntentPair, activityBId,
246                         activityCId) /* activityIntentPredicate */,
247                 parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
248                 .setDefaultSplitAttributes(new SplitAttributes.Builder().setSplitType(
249                         activityBCSplitRatio).build())
250                 .build();
251 
252         // Register the two split pair rules
253         mActivityEmbeddingComponent.setEmbeddingRules(Set.of(splitPairRuleAB, splitPairRuleBC));
254 
255         // Launch the activity A and B split and verify that the split ratio is 0.37 in
256         // {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
257         Activity activityA = startFullScreenActivityNewTask(TestActivityWithId.class, activityAId);
258         Activity activityB = startActivityAndVerifySplitAttributes(activityA,
259                 TestActivityWithId.class, splitPairRuleAB, activityBId, mSplitInfoConsumer);
260 
261         // Launch the activity B and C split and verify that the split ratio is 0.65 in
262         // {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
263         Activity activityC = startActivityAndVerifySplitAttributes(activityB,
264                 TestActivityWithId.class, splitPairRuleBC, activityCId, mSplitInfoConsumer);
265 
266         // Finish activity C so that activity A and B are in a split again. Verify that the split
267         // ratio returns to 0.37 in {@link ActivityEmbeddingUtil#assertValidSplit}.
268         activityC.finish();
269         assertValidSplit(activityA, activityB, splitPairRuleAB);
270     }
271 
272     @ApiTest(apis = "androidx.window.extensions.embedding.SplitAttributes.HingeSplitType"
273             + "#HingeSplitType")
274     @Test
testHingeSplitType()275     public void testHingeSplitType() {
276         TestConfigChangeHandlingActivity primaryActivity = startFullScreenActivityNewTask(
277                 TestConfigChangeHandlingActivity.class);
278 
279         SplitPairRule splitPairRule = createSplitPairRuleBuilder(
280                 activityActivityPair -> true,
281                 activityIntentPair -> true,
282                 windowMetrics -> true)
283                 .setDefaultSplitAttributes(HINGE_SPLIT_ATTRS)
284                 .build();
285         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
286 
287         // Start another activity to split with the primary activity and verify that the split type
288         // is hinge.
289         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
290                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
291     }
292 
293     /** Verifies {@link SplitAttributes.SplitType.ExpandContainersSplitType} behavior. */
294     @ApiTest(apis = {"androidx.window.extensions.embedding.SplitAttributes"
295             + ".ExpandContainersSplitType#ExpandContainersSplitType"})
296     @Test
testExpandSplitType()297     public void testExpandSplitType() {
298         SplitPairRule splitPairRule = createSplitPairRuleBuilder(
299                 activityActivityPair -> true,
300                 activityIntentPair -> true,
301                 windowMetrics -> true
302         )
303                 .setDefaultSplitAttributes(EXPAND_SPLIT_ATTRS)
304                 .build();
305         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
306 
307         // Start activities in a split and verify that the split type is expand,
308         // which is checked in {@link ActivityEmbeddingUtil#startActivityAndVerifySplit}.
309         Activity primaryActivity = startFullScreenActivityNewTask(
310                 TestConfigChangeHandlingActivity.class);
311         startActivityAndVerifySplitAttributes(primaryActivity, TestActivityWithId.class,
312                 splitPairRule, "secondaryActivityId", mSplitInfoConsumer);
313     }
314 
createUnevenWidthSplitPairRule(int layoutDir)315     private SplitPairRule createUnevenWidthSplitPairRule(int layoutDir) {
316         return createSplitPairRuleBuilder(
317                 activityActivityPair -> true /* activityPairPredicate */,
318                 activityIntentPair -> true /* activityIntentPredicate */,
319                 parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
320                 .setDefaultSplitAttributes(new SplitAttributes.Builder()
321                         .setSplitType(UNEVEN_CONTAINERS_DEFAULT_SPLIT_TYPE)
322                         .setLayoutDirection(layoutDir)
323                         .build())
324                 .build();
325     }
326 
matchesActivityIntentPair(@onNull Pair<Activity, Intent> activityIntentPair, @NonNull String primaryActivityId, @NonNull String secondaryActivityId)327     static boolean matchesActivityIntentPair(@NonNull Pair<Activity, Intent> activityIntentPair,
328             @NonNull String primaryActivityId, @NonNull String secondaryActivityId) {
329         if (!(activityIntentPair.first instanceof TestActivityWithId)) {
330             return false;
331         }
332         return primaryActivityId.equals(((TestActivityWithId) activityIntentPair.first).getId())
333                 && secondaryActivityId.equals(activityIntentPair.second.getStringExtra(
334                 KEY_ACTIVITY_ID));
335     }
336 }
337