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