1 /*
2  * Copyright (C) 2020 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.view.autofill;
18 
19 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
20 
21 import android.perftests.utils.SettingsHelper;
22 import android.provider.Settings;
23 import android.util.Log;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 import androidx.test.InstrumentationRegistry;
28 
29 import com.android.compatibility.common.util.Timeout;
30 
31 import org.junit.rules.TestWatcher;
32 import org.junit.runner.Description;
33 
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.TimeUnit;
36 
37 /**
38  * Custom {@link TestWatcher} that does the setup and reset tasks for the tests.
39  */
40 final class AutofillTestWatcher extends TestWatcher {
41 
42     private static final String TAG = "AutofillTestWatcher";
43     private static final long GENERIC_TIMEOUT_MS = 10_000;
44 
45     private static ServiceWatcher sServiceWatcher;
46 
47     private String mOriginalLogLevel;
48 
49     @Override
starting(Description description)50     protected void starting(Description description) {
51         super.starting(description);
52         final String testName = description.getDisplayName();
53         Log.i(TAG, "Starting " + testName);
54 
55         prepareDevice();
56         enableVerboseLog();
57         // Prepare the service before each test.
58         // Disable the current AutofillService.
59         resetAutofillService();
60         // Set MyAutofillService status enable, it can start to accept the calls.
61         enableMyAutofillService();
62         setServiceWatcher();
63     }
64 
65     @Override
finished(Description description)66     protected void finished(Description description) {
67         super.finished(description);
68         final String testName = description.getDisplayName();
69         Log.i(TAG, "Finished " + testName);
70         restoreLogLevel();
71         // Set MyAutofillService status disable, so the calls are ignored.
72         disableMyAutofillService();
73         clearServiceWatcher();
74     }
75 
waitServiceConnect()76     void waitServiceConnect() throws InterruptedException {
77         if (sServiceWatcher != null) {
78             Log.d(TAG, "waitServiceConnect()");
79             sServiceWatcher.waitOnConnected();
80         }
81     }
82 
83     /**
84      * Uses the {@code settings} binary to set the autofill service.
85      */
setAutofillService()86     void setAutofillService() {
87         String serviceName = MyAutofillService.COMPONENT_NAME;
88         SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
89                 SettingsHelper.NAMESPACE_SECURE,
90                 Settings.Secure.AUTOFILL_SERVICE,
91                 serviceName);
92         // Waits until the service is actually enabled.
93         Timeout timeout = new Timeout("CONNECTION_TIMEOUT", GENERIC_TIMEOUT_MS, 2F,
94                 GENERIC_TIMEOUT_MS);
95         try {
96             timeout.run("Enabling Autofill service", () -> {
97                 return isAutofillServiceEnabled(serviceName) ? serviceName : null;
98             });
99         } catch (Exception e) {
100             throw new AssertionError("Enabling Autofill service failed.");
101         }
102     }
103 
104     /**
105      * Uses the {@code settings} binary to reset the autofill service.
106      */
resetAutofillService()107     void resetAutofillService() {
108         SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
109                 SettingsHelper.NAMESPACE_SECURE,
110                 Settings.Secure.AUTOFILL_SERVICE);
111     }
112 
113     /**
114      * Checks whether the given service is set as the autofill service for the default user.
115      */
isAutofillServiceEnabled(String serviceName)116     private boolean isAutofillServiceEnabled(String serviceName) {
117         String actualName = SettingsHelper.get(SettingsHelper.NAMESPACE_SECURE,
118                 Settings.Secure.AUTOFILL_SERVICE);
119         return serviceName.equals(actualName);
120     }
121 
prepareDevice()122     private void prepareDevice() {
123         // Unlock screen.
124         runShellCommand("input keyevent KEYCODE_WAKEUP");
125 
126         // Dismiss keyguard, in case it's set as "Swipe to unlock".
127         runShellCommand("wm dismiss-keyguard");
128 
129         // Collapse notifications.
130         runShellCommand("cmd statusbar collapse");
131     }
132 
enableMyAutofillService()133     private void enableMyAutofillService() {
134         MyAutofillService.resetStaticState();
135         MyAutofillService.setEnabled(true);
136     }
137 
disableMyAutofillService()138     private void disableMyAutofillService() {
139         // Must disable service so calls are ignored in case of errors during the test case;
140         // otherwise, other tests will fail because these calls are made in the UI thread (as both
141         // the service, the tests, and the app run in the same process).
142         MyAutofillService.setEnabled(false);
143     }
144 
enableVerboseLog()145     private void enableVerboseLog() {
146         mOriginalLogLevel = runShellCommand("cmd autofill get log_level");
147         Log.d(TAG, "enableVerboseLog(), mOriginalLogLevel=" + mOriginalLogLevel);
148         if (!mOriginalLogLevel.equals("verbose")) {
149             runShellCommand("cmd autofill set log_level verbose");
150         }
151     }
152 
restoreLogLevel()153     private void restoreLogLevel() {
154         Log.d(TAG, "restoreLogLevel to " + mOriginalLogLevel);
155         if (!mOriginalLogLevel.equals("verbose")) {
156             runShellCommand("cmd autofill set log_level %s", mOriginalLogLevel);
157         }
158     }
159 
setServiceWatcher()160     private static void setServiceWatcher() {
161         if (sServiceWatcher == null) {
162             sServiceWatcher = new ServiceWatcher();
163         }
164     }
165 
clearServiceWatcher()166     private static void clearServiceWatcher() {
167         if (sServiceWatcher != null) {
168             sServiceWatcher = null;
169         }
170     }
171 
172     public static final class ServiceWatcher {
173         private final CountDownLatch mConnected = new CountDownLatch(1);
174 
onConnected()175         public static void onConnected() {
176             Log.i(TAG, "onConnected:  sServiceWatcher=" + sServiceWatcher);
177 
178             sServiceWatcher.mConnected.countDown();
179         }
180 
181         @NonNull
waitOnConnected()182         public void waitOnConnected() throws InterruptedException {
183             await(mConnected, "not connected");
184         }
185 
await(@onNull CountDownLatch latch, @NonNull String fmt, @Nullable Object... args)186         private void await(@NonNull CountDownLatch latch, @NonNull String fmt,
187                 @Nullable Object... args)
188                 throws InterruptedException {
189             final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
190             if (!called) {
191                 throw new IllegalStateException(String.format(fmt, args)
192                         + " in " + GENERIC_TIMEOUT_MS + "ms");
193             }
194         }
195     }
196 }
197