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