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.CannedFillResponse.ResponseType.NULL;
20 import static android.autofillservice.cts.CannedFillResponse.ResponseType.TIMEOUT;
21 import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
22 import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
23 import static android.autofillservice.cts.Helper.IDLE_UNBIND_TIMEOUT_MS;
24 import static android.autofillservice.cts.Helper.SAVE_TIMEOUT_MS;
25 import static android.autofillservice.cts.Helper.dumpAutofillService;
26 import static android.autofillservice.cts.Helper.dumpStructure;
27 
28 import static com.google.common.truth.Truth.assertWithMessage;
29 
30 import android.app.assist.AssistStructure;
31 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
32 import android.content.ComponentName;
33 import android.os.Bundle;
34 import android.os.CancellationSignal;
35 import android.service.autofill.AutofillService;
36 import android.service.autofill.Dataset;
37 import android.service.autofill.FillCallback;
38 import android.service.autofill.FillContext;
39 import android.service.autofill.FillResponse;
40 import android.service.autofill.SaveCallback;
41 import android.util.Log;
42 
43 import java.util.List;
44 import java.util.concurrent.BlockingQueue;
45 import java.util.concurrent.LinkedBlockingQueue;
46 import java.util.concurrent.TimeUnit;
47 import java.util.concurrent.atomic.AtomicReference;
48 
49 /**
50  * Implementation of {@link AutofillService} used in the tests.
51  */
52 public class InstrumentedAutoFillService extends AutofillService {
53 
54     private static final String TAG = "InstrumentedAutoFillService";
55 
56     private static final boolean DUMP_FILL_REQUESTS = false;
57     private static final boolean DUMP_SAVE_REQUESTS = false;
58 
59     private static final String STATE_CONNECTED = "CONNECTED";
60     private static final String STATE_DISCONNECTED = "DISCONNECTED";
61 
62     private static final AtomicReference<InstrumentedAutoFillService> sInstance =
63             new AtomicReference<>();
64     private static final Replier sReplier = new Replier();
65     private static final BlockingQueue<String> sConnectionStates = new LinkedBlockingQueue<>();
66 
67     private static boolean sIgnoreUnexpectedRequests = false;
68 
InstrumentedAutoFillService()69     public InstrumentedAutoFillService() {
70         sInstance.set(this);
71     }
72 
peekInstance()73     public static AutofillService peekInstance() {
74         return sInstance.get();
75     }
76 
77     @Override
onConnected()78     public void onConnected() {
79         Log.v(TAG, "onConnected(): " + sConnectionStates);
80         sConnectionStates.offer(STATE_CONNECTED);
81     }
82 
83     @Override
onDisconnected()84     public void onDisconnected() {
85         Log.v(TAG, "onDisconnected(): " + sConnectionStates);
86         sConnectionStates.offer(STATE_DISCONNECTED);
87     }
88 
89     @Override
onFillRequest(android.service.autofill.FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)90     public void onFillRequest(android.service.autofill.FillRequest request,
91             CancellationSignal cancellationSignal, FillCallback callback) {
92         if (sIgnoreUnexpectedRequests || !fromSamePackage(request.getFillContexts()))  {
93             Log.w(TAG, "Ignoring onFillRequest()");
94             return;
95         }
96         if (DUMP_FILL_REQUESTS) dumpStructure("onFillRequest()", request.getFillContexts());
97         sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
98                 cancellationSignal, callback, request.getFlags());
99     }
100 
101     @Override
onSaveRequest(android.service.autofill.SaveRequest request, SaveCallback callback)102     public void onSaveRequest(android.service.autofill.SaveRequest request,
103             SaveCallback callback) {
104         if (sIgnoreUnexpectedRequests || !fromSamePackage(request.getFillContexts())) {
105             Log.w(TAG, "Ignoring onSaveRequest()");
106             return;
107         }
108         if (DUMP_SAVE_REQUESTS) dumpStructure("onSaveRequest()", request.getFillContexts());
109         sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback);
110     }
111 
fromSamePackage(List<FillContext> contexts)112     private boolean fromSamePackage(List<FillContext> contexts) {
113         final ComponentName component = contexts.get(contexts.size() - 1).getStructure()
114                 .getActivityComponent();
115         final String actualPackage = component.getPackageName();
116         if (!actualPackage.equals(getPackageName())) {
117             Log.w(TAG, "Got request from package " + actualPackage);
118             return false;
119         }
120         return true;
121     }
122 
123     /**
124      * Sets whether unexpected calls to
125      * {@link #onFillRequest(android.service.autofill.FillRequest, CancellationSignal, FillCallback)}
126      * should throw an exception.
127      */
setIgnoreUnexpectedRequests(boolean ignore)128     public static void setIgnoreUnexpectedRequests(boolean ignore) {
129         sIgnoreUnexpectedRequests = ignore;
130     }
131 
132     /**
133      * Waits until {@link #onConnected()} is called, or fails if it times out.
134      *
135      * <p>This method is useful on tests that explicitly verifies the connection, but should be
136      * avoided in other tests, as it adds extra time to the test execution - if a text needs to
137      * block until the service receives a callback, it should use
138      * {@link Replier#getNextFillRequest()} instead.
139      */
waitUntilConnected()140     static void waitUntilConnected() throws InterruptedException {
141         final String state = sConnectionStates.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
142         if (state == null) {
143             dumpAutofillService();
144             throw new RetryableException("not connected in %d ms", CONNECTION_TIMEOUT_MS);
145         }
146         assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_CONNECTED);
147     }
148 
149     /**
150      * Waits until {@link #onDisconnected()} is called, or fails if it times out.
151      *
152      * <p>This method is useful on tests that explicitly verifies the connection, but should be
153      * avoided in other tests, as it adds extra time to the test execution.
154      */
waitUntilDisconnected()155     static void waitUntilDisconnected() throws InterruptedException {
156         final String state = sConnectionStates.poll(2 * IDLE_UNBIND_TIMEOUT_MS,
157                 TimeUnit.MILLISECONDS);
158         if (state == null) {
159             throw new RetryableException("not disconnected in %d ms", IDLE_UNBIND_TIMEOUT_MS);
160         }
161         assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_DISCONNECTED);
162     }
163 
164     /**
165      * Gets the {@link Replier} singleton.
166      */
getReplier()167     static Replier getReplier() {
168         return sReplier;
169     }
170 
resetStaticState()171     static void resetStaticState() {
172         sConnectionStates.clear();
173     }
174 
175     /**
176      * POJO representation of the contents of a
177      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
178      * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
179      */
180     static final class FillRequest {
181         final AssistStructure structure;
182         final List<FillContext> contexts;
183         final Bundle data;
184         final CancellationSignal cancellationSignal;
185         final FillCallback callback;
186         final int flags;
187 
FillRequest(List<FillContext> contexts, Bundle data, CancellationSignal cancellationSignal, FillCallback callback, int flags)188         private FillRequest(List<FillContext> contexts, Bundle data,
189                 CancellationSignal cancellationSignal, FillCallback callback, int flags) {
190             this.contexts = contexts;
191             this.data = data;
192             this.cancellationSignal = cancellationSignal;
193             this.callback = callback;
194             this.flags = flags;
195             structure = contexts.get(contexts.size() - 1).getStructure();
196         }
197     }
198 
199     /**
200      * POJO representation of the contents of a
201      * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
202      * that can be asserted at the end of a test case.
203      */
204     static final class SaveRequest {
205         final List<FillContext> contexts;
206         final AssistStructure structure;
207         final Bundle data;
208         final SaveCallback callback;
209 
SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback)210         private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback) {
211             if (contexts != null && contexts.size() > 0) {
212                 structure = contexts.get(contexts.size() - 1).getStructure();
213             } else {
214                 structure = null;
215             }
216             this.contexts = contexts;
217             this.data = data;
218             this.callback = callback;
219         }
220     }
221 
222     /**
223      * Object used to answer a
224      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
225      * CancellationSignal, FillCallback)}
226      * on behalf of a unit test method.
227      */
228     static final class Replier {
229 
230         private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
231         private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
232         private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
233 
Replier()234         private Replier() {
235         }
236 
237         /**
238          * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
239          * one {@link Dataset}.
240          */
addResponse(CannedDataset dataset)241         Replier addResponse(CannedDataset dataset) {
242             return addResponse(new CannedFillResponse.Builder()
243                     .addDataset(dataset)
244                     .build());
245         }
246 
247         /**
248          * Sets the expectation for the next {@code onFillRequest}.
249          */
addResponse(CannedFillResponse response)250         Replier addResponse(CannedFillResponse response) {
251             if (response == null) {
252                 throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
253             }
254             mResponses.add(response);
255             return this;
256         }
257 
258         /**
259          * Gets the next fill request, in the order received.
260          *
261          * <p>Typically called at the end of a test case, to assert the initial request.
262          */
getNextFillRequest()263         FillRequest getNextFillRequest() throws InterruptedException {
264             final FillRequest request = mFillRequests.poll(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
265             if (request == null) {
266                 throw new RetryableException("onFillRequest() not called in %s ms",
267                         FILL_TIMEOUT_MS);
268             }
269             return request;
270         }
271 
272         /**
273          * Asserts the total number of {@link AutofillService#onFillRequest(
274          * android.service.autofill.FillRequest,  CancellationSignal, FillCallback)}, minus those
275          * returned by {@link #getNextFillRequest()}.
276          */
assertNumberUnhandledFillRequests(int expected)277         void assertNumberUnhandledFillRequests(int expected) {
278             assertWithMessage("Invalid number of fill requests").that(mFillRequests.size())
279                     .isEqualTo(expected);
280         }
281 
282         /**
283          * Gets the next save request, in the order received.
284          *
285          * <p>Typically called at the end of a test case, to assert the initial request.
286          */
getNextSaveRequest()287         SaveRequest getNextSaveRequest() throws InterruptedException {
288             final SaveRequest request = mSaveRequests.poll(SAVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
289             if (request == null) {
290                 throw new RetryableException(
291                         "onSaveRequest() not called in %d ms", SAVE_TIMEOUT_MS);
292             }
293             return request;
294         }
295 
296         /**
297          * Asserts the total number of
298          * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
299          * minus those returned by {@link #getNextSaveRequest()}.
300          */
assertNumberUnhandledSaveRequests(int expected)301         void assertNumberUnhandledSaveRequests(int expected) {
302             assertWithMessage("Invalid number of save requests").that(mSaveRequests.size())
303                     .isEqualTo(expected);
304         }
305 
306         /**
307          * Resets its internal state.
308          */
reset()309         void reset() {
310             mResponses.clear();
311             mFillRequests.clear();
312             mSaveRequests.clear();
313         }
314 
onFillRequest(List<FillContext> contexts, Bundle data, CancellationSignal cancellationSignal, FillCallback callback, int flags)315         private void onFillRequest(List<FillContext> contexts, Bundle data,
316                 CancellationSignal cancellationSignal, FillCallback callback, int flags) {
317             try {
318                 CannedFillResponse response = null;
319                 try {
320                     response = mResponses.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
321                 } catch (InterruptedException e) {
322                     Log.w(TAG, "Interrupted getting CannedResponse: " + e);
323                     Thread.currentThread().interrupt();
324                     return;
325                 }
326                 if (response == null) {
327                     dumpStructure("onFillRequest() without response", contexts);
328                     throw new RetryableException("No CannedResponse");
329                 }
330                 if (response.getResponseType() == NULL) {
331                     Log.d(TAG, "onFillRequest(): replying with null");
332                     callback.onSuccess(null);
333                     return;
334                 }
335 
336                 if (response.getResponseType() == TIMEOUT) {
337                     Log.d(TAG, "onFillRequest(): not replying at all");
338                     return;
339                 }
340 
341                 final String failureMessage = response.getFailureMessage();
342                 if (failureMessage != null) {
343                     Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
344                     callback.onFailure(failureMessage);
345                     return;
346                 }
347 
348                 final FillResponse fillResponse = response.asFillResponse(
349                         (id) -> Helper.findNodeByResourceId(contexts, id));
350 
351                 Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
352                 callback.onSuccess(fillResponse);
353             } finally {
354                 mFillRequests.offer(new FillRequest(contexts, data, cancellationSignal, callback,
355                         flags));
356             }
357         }
358 
onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback)359         private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback) {
360             Log.d(TAG, "onSaveRequest()");
361             mSaveRequests.offer(new SaveRequest(contexts, data, callback));
362             callback.onSuccess();
363         }
364     }
365 }
366