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