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