1 /* 2 * Copyright (C) 2017 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.autofillservice.cts; 18 19 import static android.autofillservice.cts.Helper.getContext; 20 import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME; 21 import static android.content.Context.CLIPBOARD_SERVICE; 22 23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 24 25 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 26 27 import android.app.PendingIntent; 28 import android.autofillservice.cts.InstrumentedAutoFillService.Replier; 29 import android.autofillservice.cts.augmented.AugmentedAuthActivity; 30 import android.autofillservice.cts.inline.InlineUiBot; 31 import android.content.ClipboardManager; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.pm.PackageManager; 35 import android.provider.DeviceConfig; 36 import android.provider.Settings; 37 import android.service.autofill.InlinePresentation; 38 import android.util.Log; 39 import android.view.autofill.AutofillManager; 40 import android.widget.RemoteViews; 41 42 import androidx.annotation.NonNull; 43 import androidx.test.InstrumentationRegistry; 44 import androidx.test.ext.junit.runners.AndroidJUnit4; 45 46 import com.android.compatibility.common.util.DeviceConfigStateChangerRule; 47 import com.android.compatibility.common.util.RequiredFeatureRule; 48 import com.android.compatibility.common.util.RetryRule; 49 import com.android.compatibility.common.util.SafeCleanerRule; 50 import com.android.compatibility.common.util.SettingsStateKeeperRule; 51 import com.android.compatibility.common.util.TestNameUtils; 52 import com.android.cts.mockime.ImeSettings; 53 import com.android.cts.mockime.MockImeSessionRule; 54 55 import org.junit.AfterClass; 56 import org.junit.Before; 57 import org.junit.BeforeClass; 58 import org.junit.ClassRule; 59 import org.junit.Rule; 60 import org.junit.rules.RuleChain; 61 import org.junit.rules.TestRule; 62 import org.junit.runner.Description; 63 import org.junit.runner.RunWith; 64 import org.junit.runners.model.Statement; 65 66 /** 67 * Placeholder for the base class for all integration tests: 68 * 69 * <ul> 70 * <li>{@link AutoActivityLaunch} 71 * <li>{@link ManualActivityLaunch} 72 * </ul> 73 * 74 * <p>These classes provide the common infrastructure such as: 75 * 76 * <ul> 77 * <li>Preserving the autofill service settings. 78 * <li>Cleaning up test state. 79 * <li>Wrapping the test under autofill-specific test rules. 80 * <li>Launching the activity used by the test. 81 * </ul> 82 */ 83 public final class AutoFillServiceTestCase { 84 85 /** 86 * Base class for all test cases that use an {@link AutofillActivityTestRule} to 87 * launch the activity. 88 */ 89 // Must be public because of @ClassRule 90 public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity> 91 extends BaseTestCase { 92 93 /** 94 * Returns if inline suggestion is enabled. 95 */ isInlineMode()96 protected boolean isInlineMode() { 97 return false; 98 } 99 getInlineUiBot()100 protected static UiBot getInlineUiBot() { 101 return sDefaultUiBot2; 102 } 103 getDropdownUiBot()104 protected static UiBot getDropdownUiBot() { 105 return sDefaultUiBot; 106 } 107 108 @ClassRule 109 public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper = 110 sTheRealServiceSettingsKeeper; 111 AutoActivityLaunch()112 protected AutoActivityLaunch() { 113 super(sDefaultUiBot); 114 } AutoActivityLaunch(UiBot uiBot)115 protected AutoActivityLaunch(UiBot uiBot) { 116 super(uiBot); 117 } 118 119 @Override getMainTestRule()120 protected TestRule getMainTestRule() { 121 return getActivityRule(); 122 } 123 124 /** 125 * Gets the rule to launch the main activity for this test. 126 * 127 * <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise 128 * this method could return {@code null} when the rule chain that uses it is constructed. 129 * 130 */ getActivityRule()131 protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule(); 132 launchActivity(@onNull Intent intent)133 protected @NonNull A launchActivity(@NonNull Intent intent) { 134 return getActivityRule().launchActivity(intent); 135 } 136 getActivity()137 protected @NonNull A getActivity() { 138 return getActivityRule().getActivity(); 139 } 140 } 141 142 /** 143 * Base class for all test cases that don't require an {@link AutofillActivityTestRule}. 144 */ 145 // Must be public because of @ClassRule 146 public abstract static class ManualActivityLaunch extends BaseTestCase { 147 148 @ClassRule 149 public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper = 150 sTheRealServiceSettingsKeeper; 151 ManualActivityLaunch()152 protected ManualActivityLaunch() { 153 this(sDefaultUiBot); 154 } 155 ManualActivityLaunch(@onNull UiBot uiBot)156 protected ManualActivityLaunch(@NonNull UiBot uiBot) { 157 super(uiBot); 158 } 159 160 @Override getMainTestRule()161 protected TestRule getMainTestRule() { 162 // TODO: create a NoOpTestRule on common code 163 return new TestRule() { 164 165 @Override 166 public Statement apply(Statement base, Description description) { 167 // Returns a no-op statements 168 return new Statement() { 169 @Override 170 public void evaluate() throws Throwable { 171 base.evaluate(); 172 } 173 }; 174 } 175 }; 176 } 177 178 protected SimpleSaveActivity startSimpleSaveActivity() throws Exception { 179 final Intent intent = new Intent(mContext, SimpleSaveActivity.class) 180 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 181 mContext.startActivity(intent); 182 mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL); 183 return SimpleSaveActivity.getInstance(); 184 } 185 186 protected PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception { 187 final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class) 188 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 189 mContext.startActivity(intent); 190 mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL); 191 return PreSimpleSaveActivity.getInstance(); 192 } 193 } 194 195 @RunWith(AndroidJUnit4.class) 196 // Must be public because of @ClassRule 197 public abstract static class BaseTestCase { 198 199 private static final String TAG = "AutoFillServiceTestCase"; 200 201 protected static final Replier sReplier = InstrumentedAutoFillService.getReplier(); 202 203 protected static final Context sContext = getInstrumentation().getTargetContext(); 204 205 // Hack because JUnit requires that @ClassRule instance belong to a public class. 206 protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper = 207 new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) { 208 @Override 209 protected void preEvaluate(Description description) { 210 TestNameUtils.setCurrentTestClass(description.getClassName()); 211 } 212 213 @Override 214 protected void postEvaluate(Description description) { 215 TestNameUtils.setCurrentTestClass(null); 216 } 217 }; 218 219 public static final MockImeSessionRule sMockImeSessionRule = new MockImeSessionRule( 220 InstrumentationRegistry.getTargetContext(), 221 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 222 new ImeSettings.Builder().setInlineSuggestionsEnabled(true) 223 .setInlineSuggestionViewContentDesc(InlineUiBot.SUGGESTION_STRIP_DESC)); 224 225 protected static final RequiredFeatureRule sRequiredFeatureRule = 226 new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL); 227 228 private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher(); 229 230 private final RetryRule mRetryRule = 231 new RetryRule(getNumberRetries(), () -> { 232 // Between testing and retries, clean all launched activities to avoid 233 // exception: 234 // Could not launch intent Intent { ... } within 45 seconds. 235 mTestWatcher.cleanAllActivities(); 236 }); 237 238 private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG); 239 240 protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule() 241 .setDumper(mLoggingRule) 242 .run(() -> sReplier.assertNoUnhandledFillRequests()) 243 .run(() -> sReplier.assertNoUnhandledSaveRequests()) 244 .add(() -> { return sReplier.getExceptions(); }); 245 246 @Rule 247 public final RuleChain mLookAllTheseRules = RuleChain 248 // 249 // requiredFeatureRule should be first so the test can be skipped right away 250 .outerRule(getRequiredFeaturesRule()) 251 // 252 // mTestWatcher should always be one the first rules, as it defines the name of the 253 // test being ran and finishes dangling activities at the end 254 .around(mTestWatcher) 255 // 256 // sMockImeSessionRule make sure MockImeSession.create() is used to launch mock IME 257 .around(sMockImeSessionRule) 258 // 259 // mLoggingRule wraps the test but doesn't interfere with it 260 .around(mLoggingRule) 261 // 262 // mSafeCleanerRule will catch errors 263 .around(mSafeCleanerRule) 264 // 265 // mRetryRule should be closest to the main test as possible 266 .around(mRetryRule) 267 // 268 // Augmented Autofill should be disabled by default 269 .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL, 270 AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES, 271 Integer.toString(getSmartSuggestionMode()))) 272 // 273 // Finally, let subclasses add their own rules (like ActivityTestRule) 274 .around(getMainTestRule()); 275 276 277 protected final Context mContext = sContext; 278 protected final String mPackageName; 279 protected final UiBot mUiBot; 280 281 private BaseTestCase(@NonNull UiBot uiBot) { 282 mPackageName = mContext.getPackageName(); 283 mUiBot = uiBot; 284 mUiBot.reset(); 285 } 286 287 protected int getSmartSuggestionMode() { 288 return AutofillManager.FLAG_SMART_SUGGESTION_OFF; 289 } 290 291 /** 292 * Gets how many times a test should be retried. 293 * 294 * @return {@code 1} by default, unless overridden by subclasses or by a global settings 295 * named {@code CLASS_NAME + #getNumberRetries} or 296 * {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher 297 * priority). 298 */ 299 protected int getNumberRetries() { 300 final String localProp = getClass().getName() + "#getNumberRetries"; 301 final Integer localValue = getNumberRetries(localProp); 302 if (localValue != null) return localValue.intValue(); 303 304 final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries"; 305 final Integer globalValue = getNumberRetries(globalProp); 306 if (globalValue != null) return globalValue.intValue(); 307 308 return 1; 309 } 310 311 private Integer getNumberRetries(String prop) { 312 final String value = Settings.Global.getString(sContext.getContentResolver(), prop); 313 if (value != null) { 314 Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop 315 + "' global setting"); 316 try { 317 return Integer.parseInt(value); 318 } catch (Exception e) { 319 Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e); 320 } 321 } 322 return null; 323 } 324 325 /** 326 * Gets a rule that defines which features must be present for this test to run. 327 * 328 * <p>By default it returns a rule that requires {@link PackageManager#FEATURE_AUTOFILL}, 329 * but subclass can override to be more specific. 330 */ 331 @NonNull 332 protected TestRule getRequiredFeaturesRule() { 333 return sRequiredFeatureRule; 334 } 335 336 /** 337 * Gets the test-specific {@link Rule @Rule}. 338 * 339 * <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules, 340 * so the order is preserved. 341 * 342 */ 343 @NonNull 344 protected abstract TestRule getMainTestRule(); 345 346 @BeforeClass 347 public static void disableDefaultAugmentedService() { 348 Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()"); 349 Helper.setDefaultAugmentedAutofillServiceEnabled(false); 350 } 351 352 @AfterClass 353 public static void enableDefaultAugmentedService() { 354 Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()"); 355 Helper.setDefaultAugmentedAutofillServiceEnabled(true); 356 } 357 358 @Before 359 public void prepareDevice() throws Exception { 360 Log.v(TAG, "@Before: prepareDevice()"); 361 362 // Unlock screen. 363 runShellCommand("input keyevent KEYCODE_WAKEUP"); 364 365 // Dismiss keyguard, in case it's set as "Swipe to unlock". 366 runShellCommand("wm dismiss-keyguard"); 367 368 // Collapse notifications. 369 runShellCommand("cmd statusbar collapse"); 370 371 // Set orientation as portrait, otherwise some tests might fail due to elements not 372 // fitting in, IME orientation, etc... 373 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 374 375 // Wait until device is idle to avoid flakiness 376 mUiBot.waitForIdle(); 377 378 // Clear Clipboard 379 // TODO(b/117768051): remove try/catch once fixed 380 try { 381 ((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE)) 382 .clearPrimaryClip(); 383 } catch (Exception e) { 384 Log.e(TAG, "Ignoring exception clearing clipboard", e); 385 } 386 } 387 388 @Before 389 public void preTestCleanup() { 390 Log.v(TAG, "@Before: preTestCleanup()"); 391 392 prepareServicePreTest(); 393 394 InstrumentedAutoFillService.resetStaticState(); 395 AuthenticationActivity.resetStaticState(); 396 AugmentedAuthActivity.resetStaticState(); 397 sReplier.reset(); 398 } 399 400 /** 401 * Prepares the service before each test - by default, disables it 402 */ 403 protected void prepareServicePreTest() { 404 Log.v(TAG, "prepareServicePreTest(): calling disableService()"); 405 disableService(); 406 } 407 408 /** 409 * Enables the {@link InstrumentedAutoFillService} for autofill for the current user. 410 */ 411 protected void enableService() { 412 Helper.enableAutofillService(getContext(), SERVICE_NAME); 413 } 414 415 /** 416 * Disables the {@link InstrumentedAutoFillService} for autofill for the current user. 417 */ 418 protected void disableService() { 419 Helper.disableAutofillService(getContext()); 420 } 421 422 /** 423 * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user. 424 */ 425 protected void assertServiceEnabled() { 426 Helper.assertAutofillServiceStatus(SERVICE_NAME, true); 427 } 428 429 /** 430 * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user. 431 */ 432 protected void assertServiceDisabled() { 433 Helper.assertAutofillServiceStatus(SERVICE_NAME, false); 434 } 435 436 protected RemoteViews createPresentation(String message) { 437 return Helper.createPresentation(message); 438 } 439 440 protected RemoteViews createPresentationWithCancel(String message) { 441 final RemoteViews presentation = new RemoteViews(getContext() 442 .getPackageName(), R.layout.list_item_cancel); 443 presentation.setTextViewText(R.id.text1, message); 444 return presentation; 445 } 446 447 protected InlinePresentation createInlinePresentation(String message) { 448 return Helper.createInlinePresentation(message); 449 } 450 451 protected InlinePresentation createInlinePresentation(String message, 452 PendingIntent attribution) { 453 return Helper.createInlinePresentation(message, attribution); 454 } 455 456 @NonNull 457 protected AutofillManager getAutofillManager() { 458 return mContext.getSystemService(AutofillManager.class); 459 } 460 } 461 462 protected static final UiBot sDefaultUiBot = new UiBot(); 463 protected static final UiBot sDefaultUiBot2 = new InlineUiBot(); 464 465 private AutoFillServiceTestCase() { 466 throw new UnsupportedOperationException("Contain static stuff only"); 467 } 468 } 469