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