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