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