1 /* 2 * Copyright (C) 2020 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.dynamicmime.testapp.preferred; 18 19 import static android.dynamicmime.common.Constants.APPLICATION_LABEL_TEST_APP; 20 import static android.dynamicmime.common.Constants.ACTIVITY_BOTH; 21 import static android.dynamicmime.common.Constants.ACTIVITY_FIRST; 22 import static android.dynamicmime.common.Constants.APK_PREFERRED_APP; 23 import static android.dynamicmime.common.Constants.GROUP_FIRST; 24 import static android.dynamicmime.common.Constants.GROUP_SECOND; 25 import static android.dynamicmime.common.Constants.GROUP_THIRD; 26 import static android.dynamicmime.common.Constants.MIME_IMAGE_PNG; 27 import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN; 28 import static android.dynamicmime.common.Constants.PACKAGE_PREFERRED_APP; 29 30 import static org.junit.Assert.assertNotNull; 31 import static org.junit.Assert.assertNull; 32 import static org.junit.Assume.assumeTrue; 33 34 import android.content.Intent; 35 import android.content.res.Resources; 36 import android.dynamicmime.testapp.BaseDynamicMimeTest; 37 import android.dynamicmime.testapp.assertions.MimeGroupAssertions; 38 import android.dynamicmime.testapp.commands.MimeGroupCommands; 39 import android.dynamicmime.testapp.util.Utils; 40 import android.os.SystemClock; 41 42 import androidx.test.ext.junit.runners.AndroidJUnit4; 43 import androidx.test.uiautomator.By; 44 import androidx.test.uiautomator.BySelector; 45 import androidx.test.uiautomator.Direction; 46 import androidx.test.uiautomator.UiDevice; 47 import androidx.test.uiautomator.UiObject2; 48 import androidx.test.uiautomator.UiObjectNotFoundException; 49 import androidx.test.uiautomator.UiScrollable; 50 import androidx.test.uiautomator.UiSelector; 51 import androidx.test.uiautomator.Until; 52 53 import org.junit.After; 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 import java.util.concurrent.TimeUnit; 59 import java.util.regex.Pattern; 60 61 @RunWith(AndroidJUnit4.class) 62 public class PreferredActivitiesTest extends BaseDynamicMimeTest { 63 private static final String ACTION = "android.dynamicmime.preferred.TEST_ACTION"; 64 65 private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode"; 66 private static final int NAV_BAR_INTERACTION_MODE_GESTURAL = 2; 67 68 private static final String BUTTON_ALWAYS_RES_ID = ".*:id/button_always.*"; 69 private static final BySelector BUTTON_ALWAYS = By.res(Pattern.compile(BUTTON_ALWAYS_RES_ID)); 70 private static final UiSelector BUTTON_ALWAYS_UI_SELECTOR = 71 new UiSelector().resourceId(BUTTON_ALWAYS_RES_ID); 72 private static final BySelector RESOLVER_DIALOG = By.res(Pattern.compile(".*:id/contentPanel.*")); 73 74 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(60L); 75 76 private static final String FEATURE_WEARABLE = "android.hardware.type.watch"; 77 private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; 78 79 private TestStrategy mTest; 80 PreferredActivitiesTest()81 public PreferredActivitiesTest() { 82 super(MimeGroupCommands.preferredApp(context()), MimeGroupAssertions.notUsed()); 83 assumeNavigationMode(); 84 } 85 assumeNavigationMode()86 private void assumeNavigationMode() { 87 Resources res = context().getResources(); 88 int navModeResId = res.getIdentifier(NAV_BAR_INTERACTION_MODE_RES_NAME, "integer", 89 "android"); 90 int navMode = res.getInteger(navModeResId); 91 92 assumeTrue("Non-gesture navigation mode required", 93 navMode != NAV_BAR_INTERACTION_MODE_GESTURAL); 94 } 95 96 @Before setUp()97 public void setUp() { 98 Utils.installApk(APK_PREFERRED_APP); 99 } 100 101 @After tearDown()102 public void tearDown() { 103 super.tearDown(); 104 Utils.uninstallApp(PACKAGE_PREFERRED_APP); 105 } 106 107 @Test testRemoveFromGroup()108 public void testRemoveFromGroup() { 109 setStrategyAndRun(new TestStrategy() { 110 @Override 111 public void prepareMimeGroups() { 112 addMimeTypeToGroup(GROUP_FIRST, mimeType()); 113 } 114 115 @Override 116 public void changeMimeGroups() { 117 removeMimeTypeFromGroup(GROUP_FIRST, mimeType()); 118 } 119 120 @Override 121 public String preferredActivity() { 122 return ACTIVITY_FIRST; 123 } 124 125 @Override 126 public String preferredApplicationLabel() { 127 return APPLICATION_LABEL_TEST_APP; 128 } 129 }); 130 } 131 132 @Test testAddToGroup()133 public void testAddToGroup() { 134 setStrategyAndRun(new TestStrategy() { 135 @Override 136 public void prepareMimeGroups() { 137 addMimeTypeToGroup(GROUP_FIRST, mimeType()); 138 } 139 140 @Override 141 public void changeMimeGroups() { 142 addMimeTypeToGroup(GROUP_FIRST, MIME_IMAGE_PNG); 143 } 144 145 @Override 146 public void revertMimeGroupsChange() { 147 removeMimeTypeFromGroup(GROUP_FIRST, MIME_IMAGE_PNG); 148 } 149 150 @Override 151 public String preferredActivity() { 152 return ACTIVITY_FIRST; 153 } 154 155 @Override 156 public String preferredApplicationLabel() { 157 return APPLICATION_LABEL_TEST_APP; 158 } 159 }); 160 } 161 162 @Test testClearGroup()163 public void testClearGroup() { 164 setStrategyAndRun(new TestStrategy() { 165 @Override 166 public void prepareMimeGroups() { 167 addMimeTypeToGroup(GROUP_FIRST, mimeType()); 168 } 169 170 @Override 171 public void changeMimeGroups() { 172 clearMimeGroup(GROUP_FIRST); 173 } 174 175 @Override 176 public String preferredActivity() { 177 return ACTIVITY_FIRST; 178 } 179 180 @Override 181 public String preferredApplicationLabel() { 182 return APPLICATION_LABEL_TEST_APP; 183 } 184 }); 185 } 186 187 @Test testModifyGroupWithoutActualGroupChanges()188 public void testModifyGroupWithoutActualGroupChanges() { 189 setStrategyAndRun(new TestStrategy() { 190 @Override 191 public void prepareMimeGroups() { 192 addMimeTypeToGroup(GROUP_FIRST, mimeType()); 193 } 194 195 @Override 196 public void changeMimeGroups() { 197 addMimeTypeToGroup(GROUP_FIRST, mimeType()); 198 } 199 200 @Override 201 public String preferredActivity() { 202 return ACTIVITY_FIRST; 203 } 204 205 @Override 206 public String preferredApplicationLabel() { 207 return APPLICATION_LABEL_TEST_APP; 208 } 209 210 @Override 211 public boolean isActivityPreferredAfterRevert() { 212 return true; 213 } 214 215 @Override 216 public boolean isActivityPreferredAfterChange() { 217 return true; 218 } 219 }); 220 } 221 222 @Test testModifyGroupWithoutActualIntentFilterChanges()223 public void testModifyGroupWithoutActualIntentFilterChanges() { 224 setStrategyAndRun(new TestStrategy() { 225 @Override 226 public void prepareMimeGroups() { 227 addMimeTypeToGroup(GROUP_THIRD, mimeType()); 228 addMimeTypeToGroup(GROUP_SECOND, mimeType()); 229 } 230 231 @Override 232 public void changeMimeGroups() { 233 removeMimeTypeFromGroup(GROUP_THIRD, mimeType()); 234 } 235 236 @Override 237 public void revertMimeGroupsChange() { 238 addMimeTypeToGroup(GROUP_THIRD, mimeType()); 239 } 240 241 @Override 242 public String preferredActivity() { 243 return ACTIVITY_BOTH; 244 } 245 246 @Override 247 public String preferredApplicationLabel() { 248 return APPLICATION_LABEL_TEST_APP; 249 } 250 251 @Override 252 public boolean isActivityPreferredAfterRevert() { 253 return true; 254 } 255 256 @Override 257 public boolean isActivityPreferredAfterChange() { 258 return true; 259 } 260 }); 261 } 262 setStrategyAndRun(TestStrategy test)263 private void setStrategyAndRun(TestStrategy test) { 264 mTest = test; 265 runTest(); 266 } 267 runTest()268 private void runTest() { 269 mTest.prepareMimeGroups(); 270 setPreferredActivity(); 271 272 mTest.changeMimeGroups(); 273 checkPreferredActivityAfterChange(); 274 275 mTest.revertMimeGroupsChange(); 276 checkPreferredActivityAfterRevert(); 277 278 getUiDevice().pressHome(); 279 } 280 setPreferredActivity()281 private void setPreferredActivity() { 282 triggerResolutionDialog(); 283 284 verifyDialogIsShown(true); 285 286 chooseActivity("TestApp" + mTest.preferredActivity()); 287 } 288 checkPreferredActivityAfterChange()289 private void checkPreferredActivityAfterChange() { 290 checkPreferredActivity(mTest.isActivityPreferredAfterChange()); 291 } 292 checkPreferredActivityAfterRevert()293 private void checkPreferredActivityAfterRevert() { 294 checkPreferredActivity(mTest.isActivityPreferredAfterRevert()); 295 } 296 checkPreferredActivity(boolean hasPreferredActivity)297 private void checkPreferredActivity(boolean hasPreferredActivity) { 298 triggerResolutionDialog(); 299 verifyResolutionDialog(hasPreferredActivity); 300 } 301 triggerResolutionDialog()302 private void triggerResolutionDialog() { 303 getUiDevice().pressHome(); 304 sendIntent(mTest.mimeType()); 305 } 306 verifyResolutionDialog(boolean shouldLaunchActivity)307 private void verifyResolutionDialog(boolean shouldLaunchActivity) { 308 verifyDialogIsShown(!shouldLaunchActivity); 309 getUiDevice().pressBack(); 310 } 311 verifyDialogIsShown(boolean shouldBeShown)312 private void verifyDialogIsShown(boolean shouldBeShown) { 313 if (Utils.hasFeature(FEATURE_WEARABLE)) { 314 scrollToSelector(BUTTON_ALWAYS_UI_SELECTOR); 315 } 316 UiObject2 buttonAlways = getUiDevice().wait(Until.findObject(BUTTON_ALWAYS), TIMEOUT); 317 318 if (shouldBeShown) { 319 assertNotNull(buttonAlways); 320 } else { 321 assertNull(buttonAlways); 322 } 323 } 324 chooseActivity(String label)325 private void chooseActivity(String label) { 326 UiObject2 mUIObject = findObjectInDialog(label); 327 // Get UIObject based on application label, if chooser dialog displays application first 328 if (mUIObject == null) { 329 mUIObject = findObjectInDialog(mTest.preferredApplicationLabel()); 330 mUIObject.click(); 331 mUIObject = findObjectInDialog(label); 332 } 333 mUIObject.click(); 334 chooseUseAlways(); 335 336 getUiDevice().pressBack(); 337 } 338 findObjectInDialog(String label)339 private UiObject2 findObjectInDialog(String label) { 340 if (!Utils.hasFeature(FEATURE_WEARABLE) && !Utils.hasFeature(FEATURE_AUTOMOTIVE)) { 341 getUiDevice() 342 .wait(Until.findObject(RESOLVER_DIALOG), TIMEOUT) 343 .swipe(Direction.UP, 1f); 344 } else { 345 scrollToSelector(new UiSelector().text(label)); 346 } 347 return getUiDevice().findObject(By.text(label)); 348 } 349 chooseUseAlways()350 private void chooseUseAlways() { 351 if (Utils.hasFeature(FEATURE_WEARABLE)) { 352 scrollToSelector(BUTTON_ALWAYS_UI_SELECTOR); 353 } 354 getUiDevice() 355 .wait(Until.findObject(BUTTON_ALWAYS), TIMEOUT) 356 .click(); 357 } 358 scrollToSelector(UiSelector selector)359 private void scrollToSelector(UiSelector selector) { 360 try { 361 int resId = Resources.getSystem().getIdentifier( 362 "config_customResolverActivity", "string", "android"); 363 String customResolverActivity = context().getString(resId); 364 String customResolverPackageName; 365 if (customResolverActivity.isEmpty()) { 366 // If custom resolver is not in use, it'll be using the Android default 367 customResolverPackageName = "android"; 368 } else { 369 customResolverPackageName = customResolverActivity.split("/")[0]; 370 } 371 372 UiSelector scrollableSelector = 373 new UiSelector() 374 .scrollable(true) 375 .packageName(customResolverPackageName); 376 UiScrollable scrollable = new UiScrollable(scrollableSelector); 377 scrollable.waitForExists(TIMEOUT); 378 if (scrollable.exists()) { 379 scrollable.scrollToBeginning(Integer.MAX_VALUE); 380 scrollable.scrollIntoView(selector); 381 SystemClock.sleep(1000L); 382 } 383 } catch (UiObjectNotFoundException ignore) { 384 throw new AssertionError("Scrollable view was lost."); 385 } 386 } 387 388 private interface TestStrategy { prepareMimeGroups()389 void prepareMimeGroups(); 390 changeMimeGroups()391 void changeMimeGroups(); 392 revertMimeGroupsChange()393 default void revertMimeGroupsChange() { 394 prepareMimeGroups(); 395 } 396 mimeType()397 default String mimeType() { 398 return MIME_TEXT_PLAIN; 399 } 400 preferredActivity()401 String preferredActivity(); 402 preferredApplicationLabel()403 String preferredApplicationLabel(); 404 isActivityPreferredAfterChange()405 default boolean isActivityPreferredAfterChange() { 406 return false; 407 } 408 isActivityPreferredAfterRevert()409 default boolean isActivityPreferredAfterRevert() { 410 return false; 411 } 412 } 413 getUiDevice()414 private static UiDevice getUiDevice() { 415 return UiDevice.getInstance(instrumentation()); 416 } 417 sendIntent(String mimeType)418 private static void sendIntent(String mimeType) { 419 Intent sendIntent = new Intent(); 420 sendIntent.setAction(ACTION); 421 sendIntent.setType(mimeType); 422 sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 423 targetContext().startActivity(sendIntent, null); 424 } 425 } 426