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.dumpStructure;
22 import static android.autofillservice.cts.Helper.getActivityName;
23 import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
24 import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
25 import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
26 import static android.autofillservice.cts.Timeouts.IDLE_UNBIND_TIMEOUT;
27 import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import android.app.assist.AssistStructure;
32 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
33 import android.content.ComponentName;
34 import android.content.IntentSender;
35 import android.os.Bundle;
36 import android.os.CancellationSignal;
37 import android.os.SystemClock;
38 import android.service.autofill.AutofillService;
39 import android.service.autofill.Dataset;
40 import android.service.autofill.FillCallback;
41 import android.service.autofill.FillContext;
42 import android.service.autofill.FillEventHistory;
43 import android.service.autofill.FillEventHistory.Event;
44 import android.service.autofill.FillResponse;
45 import android.service.autofill.SaveCallback;
46 import android.util.Log;
47 
48 import androidx.annotation.Nullable;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.concurrent.BlockingQueue;
53 import java.util.concurrent.LinkedBlockingQueue;
54 import java.util.concurrent.TimeUnit;
55 import java.util.concurrent.atomic.AtomicReference;
56 
57 /**
58  * Implementation of {@link AutofillService} used in the tests.
59  */
60 public class InstrumentedAutoFillService extends AutofillService {
61 
62     static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
63     static final String SERVICE_CLASS = "InstrumentedAutoFillService";
64 
65     static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
66 
67     private static final String TAG = "InstrumentedAutoFillService";
68 
69     private static final boolean DUMP_FILL_REQUESTS = false;
70     private static final boolean DUMP_SAVE_REQUESTS = false;
71 
72     protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
73             new AtomicReference<>();
74     private static final Replier sReplier = new Replier();
75 
76     private static final Object sLock = new Object();
77 
78     // @GuardedBy("sLock") // NOTE: not using annotation because of dependencies
79     private static boolean sIgnoreUnexpectedRequests = false;
80 
81     // @GuardedBy("sLock") // NOTE: not using annotation because of dependencies
82     private static boolean sConnected;
83 
84     protected static String sServiceLabel = SERVICE_CLASS;
85 
InstrumentedAutoFillService()86     public InstrumentedAutoFillService() {
87         sInstance.set(this);
88         sServiceLabel = SERVICE_CLASS;
89     }
90 
peekInstance()91     private static InstrumentedAutoFillService peekInstance() {
92         return sInstance.get();
93     }
94 
95     /**
96      * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
97      * expected size.
98      */
getFillEvents(int expectedSize)99     public static List<Event> getFillEvents(int expectedSize) throws Exception {
100         final List<Event> events = getFillEventHistory(expectedSize).getEvents();
101         // Sanity check
102         if (expectedSize > 0 && events == null || events.size() != expectedSize) {
103             throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
104                     + ", but it is: " + events);
105         }
106         return events;
107     }
108 
109     /**
110      * Gets the {@link FillEventHistory}, waiting until it has the expected size.
111      */
getFillEventHistory(int expectedSize)112     public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
113         final InstrumentedAutoFillService service = peekInstance();
114 
115         if (expectedSize == 0) {
116             // Need to always sleep as there is no condition / callback to be used to wait until
117             // expected number of events is set.
118             SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
119             final FillEventHistory history = service.getFillEventHistory();
120             assertThat(history.getEvents()).isNull();
121             return history;
122         }
123 
124         return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
125             final FillEventHistory history = service.getFillEventHistory();
126             if (history == null) {
127                 return null;
128             }
129             final List<Event> events = history.getEvents();
130             if (events != null) {
131                 if (events.size() != expectedSize) {
132                     Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
133                     return null;
134                 }
135             } else {
136                 Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
137                 return null;
138             }
139             return history;
140         });
141     }
142 
143     /**
144      * Asserts there is no {@link FillEventHistory}.
145      */
assertNoFillEventHistory()146     public static void assertNoFillEventHistory() {
147         // Need to always sleep as there is no condition / callback to be used to wait until
148         // expected number of events is set.
149         SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
150         assertThat(peekInstance().getFillEventHistory()).isNull();
151 
152     }
153 
154     /**
155      * Gets the service label associated with the current instance.
156      */
getServiceLabel()157     public static String getServiceLabel() {
158         return sServiceLabel;
159     }
160 
161     @Override
onConnected()162     public void onConnected() {
163         synchronized (sLock) {
164             Log.v(TAG, "onConnected(): connected=" + sConnected);
165             sConnected = true;
166         }
167     }
168 
169     @Override
onDisconnected()170     public void onDisconnected() {
171         synchronized (sLock) {
172             Log.v(TAG, "onDisconnected(): connected=" + sConnected);
173             sConnected = false;
174         }
175     }
176 
177     @Override
onFillRequest(android.service.autofill.FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)178     public void onFillRequest(android.service.autofill.FillRequest request,
179             CancellationSignal cancellationSignal, FillCallback callback) {
180         if (DUMP_FILL_REQUESTS) dumpStructure("onFillRequest()", request.getFillContexts());
181         synchronized (sLock) {
182             if (sIgnoreUnexpectedRequests || !fromSamePackage(request.getFillContexts()))  {
183                 Log.w(TAG, "Ignoring onFillRequest()");
184                 return;
185             }
186         }
187         sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
188                 cancellationSignal, callback, request.getFlags());
189     }
190 
191     @Override
onSaveRequest(android.service.autofill.SaveRequest request, SaveCallback callback)192     public void onSaveRequest(android.service.autofill.SaveRequest request,
193             SaveCallback callback) {
194         if (DUMP_SAVE_REQUESTS) dumpStructure("onSaveRequest()", request.getFillContexts());
195         synchronized (sLock) {
196             if (sIgnoreUnexpectedRequests || !fromSamePackage(request.getFillContexts())) {
197                 Log.w(TAG, "Ignoring onSaveRequest()");
198                 return;
199             }
200         }
201         sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback,
202                 request.getDatasetIds());
203     }
204 
isConnected()205     private static boolean isConnected() {
206         synchronized (sLock) {
207             return sConnected;
208         }
209     }
210 
fromSamePackage(List<FillContext> contexts)211     private boolean fromSamePackage(List<FillContext> contexts) {
212         final ComponentName component = contexts.get(contexts.size() - 1).getStructure()
213                 .getActivityComponent();
214         final String actualPackage = component.getPackageName();
215         if (!actualPackage.equals(getPackageName())
216                 && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
217             Log.w(TAG, "Got request from package " + actualPackage);
218             return false;
219         }
220         return true;
221     }
222 
223     /**
224      * Sets whether unexpected calls to
225      * {@link #onFillRequest(android.service.autofill.FillRequest, CancellationSignal, FillCallback)}
226      * should throw an exception.
227      */
setIgnoreUnexpectedRequests(boolean ignore)228     public static void setIgnoreUnexpectedRequests(boolean ignore) {
229         synchronized (sLock) {
230             sIgnoreUnexpectedRequests = ignore;
231         }
232     }
233 
234     /**
235      * Waits until {@link #onConnected()} is called, or fails if it times out.
236      *
237      * <p>This method is useful on tests that explicitly verifies the connection, but should be
238      * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
239      * where the service might have being disconnected already; for example, if the fill request
240      * was replied with a {@code null} response) - if a text needs to block until the service
241      * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
242      */
waitUntilConnected()243     static void waitUntilConnected() throws Exception {
244         waitConnectionState(CONNECTION_TIMEOUT, true);
245     }
246 
247     /**
248      * Waits until {@link #onDisconnected()} is called, or fails if it times out.
249      *
250      * <p>This method is useful on tests that explicitly verifies the connection, but should be
251      * avoided in other tests, as it adds extra time to the test execution.
252      */
waitUntilDisconnected()253     static void waitUntilDisconnected() throws Exception {
254         waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
255     }
256 
waitConnectionState(Timeout timeout, boolean expected)257     private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
258         timeout.run("wait for connected=" + expected,  () -> {
259             return isConnected() == expected ? Boolean.TRUE : null;
260         });
261     }
262 
263     /**
264      * Gets the {@link Replier} singleton.
265      */
getReplier()266     static Replier getReplier() {
267         return sReplier;
268     }
269 
resetStaticState()270     static void resetStaticState() {
271         sInstance.set(null);
272         sConnected = false;
273         sServiceLabel = SERVICE_CLASS;
274     }
275 
276     /**
277      * POJO representation of the contents of a
278      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
279      * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
280      */
281     static final class FillRequest {
282         final AssistStructure structure;
283         final List<FillContext> contexts;
284         final Bundle data;
285         final CancellationSignal cancellationSignal;
286         final FillCallback callback;
287         final int flags;
288 
FillRequest(List<FillContext> contexts, Bundle data, CancellationSignal cancellationSignal, FillCallback callback, int flags)289         private FillRequest(List<FillContext> contexts, Bundle data,
290                 CancellationSignal cancellationSignal, FillCallback callback, int flags) {
291             this.contexts = contexts;
292             this.data = data;
293             this.cancellationSignal = cancellationSignal;
294             this.callback = callback;
295             this.flags = flags;
296             structure = contexts.get(contexts.size() - 1).getStructure();
297         }
298 
299         @Override
toString()300         public String toString() {
301             return "FillRequest:" + getActivityName(contexts);
302         }
303     }
304 
305     /**
306      * POJO representation of the contents of a
307      * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
308      * that can be asserted at the end of a test case.
309      */
310     static final class SaveRequest {
311         public final List<FillContext> contexts;
312         public final AssistStructure structure;
313         public final Bundle data;
314         public final SaveCallback callback;
315         public final List<String> datasetIds;
316 
SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback, List<String> datasetIds)317         private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
318                 List<String> datasetIds) {
319             if (contexts != null && contexts.size() > 0) {
320                 structure = contexts.get(contexts.size() - 1).getStructure();
321             } else {
322                 structure = null;
323             }
324             this.contexts = contexts;
325             this.data = data;
326             this.callback = callback;
327             this.datasetIds = datasetIds;
328         }
329 
330         @Override
toString()331         public String toString() {
332             return "SaveRequest:" + getActivityName(contexts);
333         }
334     }
335 
336     /**
337      * Object used to answer a
338      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
339      * CancellationSignal, FillCallback)}
340      * on behalf of a unit test method.
341      */
342     static final class Replier {
343 
344         private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
345         private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
346         private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
347 
348         private List<Throwable> mExceptions;
349         private IntentSender mOnSaveIntentSender;
350         private String mAcceptedPackageName;
351 
352         private boolean mReportUnhandledFillRequest = true;
353         private boolean mReportUnhandledSaveRequest = true;
354 
Replier()355         private Replier() {
356         }
357 
358         private IdMode mIdMode = IdMode.RESOURCE_ID;
359 
setIdMode(IdMode mode)360         public void setIdMode(IdMode mode) {
361             this.mIdMode = mode;
362         }
363 
acceptRequestsFromPackage(String packageName)364         public void acceptRequestsFromPackage(String packageName) {
365             mAcceptedPackageName = packageName;
366         }
367 
368         /**
369          * Gets the exceptions thrown asynchronously, if any.
370          */
getExceptions()371         @Nullable List<Throwable> getExceptions() {
372             return mExceptions;
373         }
374 
addException(@ullable Throwable e)375         private void addException(@Nullable Throwable e) {
376             if (e == null) return;
377 
378             if (mExceptions == null) {
379                 mExceptions = new ArrayList<>();
380             }
381             mExceptions.add(e);
382         }
383 
384         /**
385          * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
386          * one {@link Dataset}.
387          */
addResponse(CannedDataset dataset)388         Replier addResponse(CannedDataset dataset) {
389             return addResponse(new CannedFillResponse.Builder()
390                     .addDataset(dataset)
391                     .build());
392         }
393 
394         /**
395          * Sets the expectation for the next {@code onFillRequest}.
396          */
addResponse(CannedFillResponse response)397         Replier addResponse(CannedFillResponse response) {
398             if (response == null) {
399                 throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
400             }
401             mResponses.add(response);
402             return this;
403         }
404 
405         /**
406          * Sets the {@link IntentSender} that is passed to
407          * {@link SaveCallback#onSuccess(IntentSender)}.
408          */
setOnSave(IntentSender intentSender)409         void setOnSave(IntentSender intentSender) {
410             mOnSaveIntentSender = intentSender;
411         }
412 
413         /**
414          * Gets the next fill request, in the order received.
415          *
416          * <p>Typically called at the end of a test case, to assert the initial request.
417          */
getNextFillRequest()418         FillRequest getNextFillRequest() {
419             FillRequest request;
420             try {
421                 request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
422             } catch (InterruptedException e) {
423                 Thread.currentThread().interrupt();
424                 throw new IllegalStateException("Interrupted", e);
425             }
426             if (request == null) {
427                 throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
428             }
429             return request;
430         }
431 
432         /**
433          * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
434          * was not called.
435          *
436          * <p>Should only be called in cases where it's not expected to be called, as it will
437          * sleep for a few ms.
438          */
assertOnFillRequestNotCalled()439         void assertOnFillRequestNotCalled() {
440             SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
441             assertThat(mFillRequests).isEmpty();
442         }
443 
444         /**
445          * Asserts all {@link AutofillService#onFillRequest(
446          * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
447          * received by the service were properly {@link #getNextFillRequest() handled} by the test
448          * case.
449          */
assertNoUnhandledFillRequests()450         void assertNoUnhandledFillRequests() {
451             if (mFillRequests.isEmpty()) return; // Good job, test case!
452 
453             if (!mReportUnhandledFillRequest) {
454                 // Just log, so it's not thrown again on @After if already thrown on main body
455                 Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
456                         + "but logging just in case: " + mFillRequests);
457                 return;
458             }
459 
460             mReportUnhandledFillRequest = false;
461             throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
462                     + mFillRequests);
463         }
464 
465         /**
466          * Gets the current number of unhandled requests.
467          */
getNumberUnhandledFillRequests()468         int getNumberUnhandledFillRequests() {
469             return mFillRequests.size();
470         }
471 
472         /**
473          * Gets the next save request, in the order received.
474          *
475          * <p>Typically called at the end of a test case, to assert the initial request.
476          */
getNextSaveRequest()477         SaveRequest getNextSaveRequest() {
478             SaveRequest request;
479             try {
480                 request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
481             } catch (InterruptedException e) {
482                 Thread.currentThread().interrupt();
483                 throw new IllegalStateException("Interrupted", e);
484             }
485             if (request == null) {
486                 throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
487             }
488             return request;
489         }
490 
491         /**
492          * Asserts all
493          * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
494          * save requests} received by the service were properly
495          * {@link #getNextFillRequest() handled} by the test case.
496          */
assertNoUnhandledSaveRequests()497         void assertNoUnhandledSaveRequests() {
498             if (mSaveRequests.isEmpty()) return; // Good job, test case!
499 
500             if (!mReportUnhandledSaveRequest) {
501                 // Just log, so it's not thrown again on @After if already thrown on main body
502                 Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
503                         + "but logging just in case: " + mSaveRequests);
504                 return;
505             }
506 
507             mReportUnhandledSaveRequest = false;
508             throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
509                     + mSaveRequests);
510         }
511 
512         /**
513          * Resets its internal state.
514          */
reset()515         void reset() {
516             mResponses.clear();
517             mFillRequests.clear();
518             mSaveRequests.clear();
519             mExceptions = null;
520             mOnSaveIntentSender = null;
521             mAcceptedPackageName = null;
522             mReportUnhandledFillRequest = true;
523             mReportUnhandledSaveRequest = true;
524         }
525 
onFillRequest(List<FillContext> contexts, Bundle data, CancellationSignal cancellationSignal, FillCallback callback, int flags)526         private void onFillRequest(List<FillContext> contexts, Bundle data,
527                 CancellationSignal cancellationSignal, FillCallback callback, int flags) {
528             try {
529                 CannedFillResponse response = null;
530                 try {
531                     response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
532                 } catch (InterruptedException e) {
533                     Log.w(TAG, "Interrupted getting CannedResponse: " + e);
534                     Thread.currentThread().interrupt();
535                     addException(e);
536                     return;
537                 }
538                 if (response == null) {
539                     final String activityName = getActivityName(contexts);
540                     final String msg = "onFillRequest() for activity " + activityName
541                             + " received when no canned response was set.";
542                     dumpStructure(msg, contexts);
543                     return;
544                 }
545                 if (response.getResponseType() == NULL) {
546                     Log.d(TAG, "onFillRequest(): replying with null");
547                     callback.onSuccess(null);
548                     return;
549                 }
550 
551                 if (response.getResponseType() == TIMEOUT) {
552                     Log.d(TAG, "onFillRequest(): not replying at all");
553                     return;
554                 }
555 
556                 final String failureMessage = response.getFailureMessage();
557                 if (failureMessage != null) {
558                     Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
559                     callback.onFailure(failureMessage);
560                     return;
561                 }
562 
563                 final FillResponse fillResponse;
564 
565                 switch (mIdMode) {
566                     case RESOURCE_ID:
567                         fillResponse = response.asFillResponse(
568                                 (id) -> Helper.findNodeByResourceId(contexts, id));
569                         break;
570                     case HTML_NAME:
571                         fillResponse = response.asFillResponse(
572                                 (name) -> Helper.findNodeByHtmlName(contexts, name));
573                         break;
574                     case HTML_NAME_OR_RESOURCE_ID:
575                         fillResponse = response.asFillResponse(
576                                 (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
577                         break;
578                     default:
579                         throw new IllegalStateException("Unknown id mode: " + mIdMode);
580                 }
581 
582                 Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
583                 callback.onSuccess(fillResponse);
584             } catch (Throwable t) {
585                 addException(t);
586             } finally {
587                 mFillRequests.offer(new FillRequest(contexts, data, cancellationSignal, callback,
588                         flags));
589             }
590         }
591 
onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback, List<String> datasetIds)592         private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
593                 List<String> datasetIds) {
594             Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
595             mSaveRequests.offer(new SaveRequest(contexts, data, callback, datasetIds));
596             if (mOnSaveIntentSender != null) {
597                 callback.onSuccess(mOnSaveIntentSender);
598             } else {
599                 callback.onSuccess();
600             }
601         }
602     }
603 }
604