• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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