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