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