• 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.testcore;
18 
19 import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.FAILURE;
20 import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
21 import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.TIMEOUT;
22 import static android.autofillservice.cts.testcore.Helper.dumpStructure;
23 import static android.autofillservice.cts.testcore.Helper.getActivityName;
24 import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
25 import static android.autofillservice.cts.testcore.Timeouts.FILL_EVENTS_TIMEOUT;
26 import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
27 import static android.autofillservice.cts.testcore.Timeouts.IDLE_UNBIND_TIMEOUT;
28 import static android.autofillservice.cts.testcore.Timeouts.SAVE_TIMEOUT;
29 
30 import static com.google.common.truth.Truth.assertThat;
31 
32 import android.app.assist.AssistStructure;
33 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
34 import android.autofillservice.cts.testcore.CannedFillResponse.ResponseType;
35 import android.content.ComponentName;
36 import android.content.IntentSender;
37 import android.os.Bundle;
38 import android.os.CancellationSignal;
39 import android.os.Handler;
40 import android.os.HandlerThread;
41 import android.os.SystemClock;
42 import android.service.autofill.AutofillService;
43 import android.service.autofill.Dataset;
44 import android.service.autofill.FillCallback;
45 import android.service.autofill.FillContext;
46 import android.service.autofill.FillEventHistory;
47 import android.service.autofill.FillEventHistory.Event;
48 import android.service.autofill.FillResponse;
49 import android.service.autofill.SaveCallback;
50 import android.service.autofill.SavedDatasetsInfoCallback;
51 import android.util.Log;
52 import android.view.inputmethod.InlineSuggestionsRequest;
53 
54 import androidx.annotation.NonNull;
55 import androidx.annotation.Nullable;
56 
57 import com.android.compatibility.common.util.RetryableException;
58 import com.android.compatibility.common.util.TestNameUtils;
59 import com.android.compatibility.common.util.Timeout;
60 
61 import java.io.FileDescriptor;
62 import java.io.IOException;
63 import java.io.PrintWriter;
64 import java.io.StringWriter;
65 import java.util.ArrayList;
66 import java.util.List;
67 import java.util.concurrent.BlockingQueue;
68 import java.util.concurrent.LinkedBlockingQueue;
69 import java.util.concurrent.TimeUnit;
70 import java.util.concurrent.atomic.AtomicBoolean;
71 import java.util.concurrent.atomic.AtomicReference;
72 import java.util.function.Consumer;
73 import java.util.function.Function;
74 
75 /**
76  * Implementation of {@link AutofillService} used in the tests.
77  */
78 public class InstrumentedAutoFillService extends AutofillService {
79 
80     public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
81     public static final String SERVICE_CLASS = "InstrumentedAutoFillService";
82 
83     public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
84 
85     public static String sServiceLabel = SERVICE_CLASS;
86 
87     // TODO(b/125844305): remove once fixed
88     private static final boolean FAIL_ON_INVALID_CONNECTION_STATE = false;
89 
90     private static final String TAG = "InstrumentedAutoFillService";
91 
92     private static final boolean DUMP_FILL_REQUESTS = false;
93     private static final boolean DUMP_SAVE_REQUESTS = false;
94 
95     protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
96             new AtomicReference<>();
97     private static final Replier sReplier = new Replier();
98     @Nullable
99     private static Consumer<SavedDatasetsInfoCallback> sSavedDatasetsInfoReplier;
100 
101     private static AtomicBoolean sConnected = new AtomicBoolean(false);
102     private static AtomicBoolean sTestRunning = new AtomicBoolean(false);
103 
104     // We must handle all requests in a separate thread as the service's main thread is the also
105     // the UI thread of the test process and we don't want to hose it in case of failures here
106     private static final HandlerThread sMyThread = new HandlerThread("MyServiceThread");
107     private final Handler mHandler;
108 
109     private boolean mConnected;
110 
111     static {
Log.i(TAG, "Starting thread " + sMyThread)112         Log.i(TAG, "Starting thread " + sMyThread);
sMyThread.start()113         sMyThread.start();
114     }
115 
InstrumentedAutoFillService()116     public InstrumentedAutoFillService() {
117         Log.v(TAG, "constructor " + SERVICE_CLASS + " " + sConnected.get());
118 
119         mHandler = Handler.createAsync(sMyThread.getLooper());
120 
121         if (!sConnected.get()) {
122             sInstance.set(this);
123             sReplier.setHandler(mHandler);
124         }
125 
126         // Only set class variable after/before a test
127         if (!sTestRunning.get()) {
128             sServiceLabel = SERVICE_CLASS;
129         }
130     }
131 
peekInstance()132     private static InstrumentedAutoFillService peekInstance() {
133         return sInstance.get();
134     }
135 
136     /**
137      * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
138      * expected size.
139      */
getFillEvents(int expectedSize)140     public static List<Event> getFillEvents(int expectedSize) throws Exception {
141         final List<Event> events = getFillEventHistory(expectedSize).getEvents();
142         // Validation check
143         if (expectedSize > 0 && events == null || events.size() != expectedSize) {
144             throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
145                     + ", but it is: " + events);
146         }
147         return events;
148     }
149 
150     /**
151      * Gets the {@link FillEventHistory}, waiting until it has the expected size.
152      */
getFillEventHistory(int expectedSize)153     public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
154         final InstrumentedAutoFillService service = peekInstance();
155 
156         if (expectedSize == 0) {
157             // Need to always sleep as there is no condition / callback to be used to wait until
158             // expected number of events is set.
159             SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
160             final FillEventHistory history = service.getFillEventHistory();
161             assertThat(history.getEvents()).isNull();
162             return history;
163         }
164 
165         return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
166             final FillEventHistory history = service.getFillEventHistory();
167             if (history == null) {
168                 return null;
169             }
170             final List<Event> events = history.getEvents();
171             if (events != null) {
172                 if (events.size() != expectedSize) {
173                     Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
174                     return null;
175                 }
176             } else {
177                 Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
178                 return null;
179             }
180             return history;
181         });
182     }
183 
184     /**
185      * Asserts there is no {@link FillEventHistory}.
186      */
assertNoFillEventHistory()187     public static void assertNoFillEventHistory() {
188         // Need to always sleep as there is no condition / callback to be used to wait until
189         // expected number of events is set.
190         SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
191         assertThat(peekInstance().getFillEventHistory()).isNull();
192     }
193 
194     /**
195      * Gets the service label associated with the current instance.
196      */
getServiceLabel()197     public static String getServiceLabel() {
198         return sServiceLabel;
199     }
200 
handleConnected(boolean connected)201     private void handleConnected(boolean connected) {
202         Log.v(TAG, "handleConnected(): from " + sConnected.get() + " to " + connected);
203         sConnected.set(connected);
204     }
205 
206     @Override
onSessionDestroyed(@ullable FillEventHistory history)207     public void onSessionDestroyed(@Nullable FillEventHistory history) {
208         Log.v(TAG, "onSessionDestroyed() called");
209         mHandler.post(() -> sReplier.addLastFillEventHistory(history));
210     }
211 
212     @Override
onConnected()213     public void onConnected() {
214         Log.v(TAG, "onConnected");
215         if (mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
216             dumpSelf();
217             sReplier.addException(new IllegalStateException("onConnected() called again"));
218         }
219         mConnected = true;
220         mHandler.post(() -> handleConnected(true));
221     }
222 
223     @Override
onDisconnected()224     public void onDisconnected() {
225         Log.v(TAG, "onDisconnected");
226         if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
227             dumpSelf();
228             sReplier.addException(
229                     new IllegalStateException("onDisconnected() called when disconnected"));
230         }
231         mConnected = false;
232         mHandler.post(() -> handleConnected(false));
233     }
234 
235     @Override
onFillRequest(android.service.autofill.FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)236     public void onFillRequest(android.service.autofill.FillRequest request,
237             CancellationSignal cancellationSignal, FillCallback callback) {
238         final ComponentName component = getLastActivityComponent(request.getFillContexts());
239         if (DUMP_FILL_REQUESTS) {
240             dumpStructure("onFillRequest()", request.getFillContexts());
241         } else {
242             Log.i(TAG, "onFillRequest() for " + component.toShortString());
243         }
244         if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
245             dumpSelf();
246             sReplier.addException(
247                     new IllegalStateException("onFillRequest() called when disconnected"));
248         }
249 
250         if (!TestNameUtils.isRunningTest()) {
251             Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
252             return;
253         }
254         if (!fromSamePackage(component))  {
255             Log.w(TAG, "Ignoring onFillRequest() from different package: " + component);
256             return;
257         }
258         mHandler.post(
259                 () -> sReplier.onFillRequest(request.getFillContexts(), request.getHints(),
260                         request.getClientState(),
261                         cancellationSignal, callback, request.getFlags(),
262                         request.getInlineSuggestionsRequest(),
263                         request.getDelayedFillIntentSender(),
264                         request.getId()));
265     }
266 
267     @Override
onSaveRequest(android.service.autofill.SaveRequest request, SaveCallback callback)268     public void onSaveRequest(android.service.autofill.SaveRequest request,
269             SaveCallback callback) {
270         if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
271             dumpSelf();
272             sReplier.addException(
273                     new IllegalStateException("onSaveRequest() called when disconnected"));
274         }
275         mHandler.post(()->handleSaveRequest(request, callback));
276     }
277 
handleSaveRequest(android.service.autofill.SaveRequest request, SaveCallback callback)278     private void handleSaveRequest(android.service.autofill.SaveRequest request,
279             SaveCallback callback) {
280         final ComponentName component = getLastActivityComponent(request.getFillContexts());
281         if (!TestNameUtils.isRunningTest()) {
282             Log.e(TAG, "onSaveRequest(" + component + ") called after tests finished");
283             return;
284         }
285         if (!fromSamePackage(component)) {
286             Log.w(TAG, "Ignoring onSaveRequest() from different package: " + component);
287             return;
288         }
289         if (DUMP_SAVE_REQUESTS) {
290             dumpStructure("onSaveRequest()", request.getFillContexts());
291         } else {
292             Log.i(TAG, "onSaveRequest() for " + component.toShortString());
293         }
294         mHandler.post(() -> sReplier.onSaveRequest(request.getFillContexts(),
295                 request.getClientState(), callback,
296                 request.getDatasetIds()));
297     }
298 
299     @Override
onSavedDatasetsInfoRequest(@onNull SavedDatasetsInfoCallback callback)300     public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) {
301         if (sSavedDatasetsInfoReplier == null) {
302             super.onSavedDatasetsInfoRequest(callback);
303         } else {
304             sSavedDatasetsInfoReplier.accept(callback);
305         }
306     }
307 
setSavedDatasetsInfoReplier( @ullable Consumer<SavedDatasetsInfoCallback> savedDatasetsInfoReplier)308     public static void setSavedDatasetsInfoReplier(
309             @Nullable Consumer<SavedDatasetsInfoCallback> savedDatasetsInfoReplier) {
310         sSavedDatasetsInfoReplier = savedDatasetsInfoReplier;
311     }
312 
isConnected()313     public static boolean isConnected() {
314         return sConnected.get();
315     }
316 
fromSamePackage(ComponentName component)317     private boolean fromSamePackage(ComponentName component) {
318         final String actualPackage = component.getPackageName();
319         if (!actualPackage.equals(getPackageName())
320                 && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
321             Log.w(TAG, "Got request from package " + actualPackage);
322             return false;
323         }
324         return true;
325     }
326 
getLastActivityComponent(List<FillContext> contexts)327     private ComponentName getLastActivityComponent(List<FillContext> contexts) {
328         return contexts.get(contexts.size() - 1).getStructure().getActivityComponent();
329     }
330 
dumpSelf()331     private void dumpSelf()  {
332         try {
333             try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
334                 dump(null, pw, null);
335                 pw.flush();
336                 final String dump = sw.toString();
337                 Log.e(TAG, "dumpSelf(): " + dump);
338             }
339         } catch (IOException e) {
340             Log.e(TAG, "I don't always fail to dump, but when I do, I dump the failure", e);
341         }
342     }
343 
344     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)345     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
346         pw.print("sConnected: "); pw.println(sConnected);
347         pw.print("mConnected: "); pw.println(mConnected);
348         pw.print("sInstance: "); pw.println(sInstance);
349         pw.println("sReplier: "); sReplier.dump(pw);
350     }
351 
352     /**
353      * Waits until {@link #onConnected()} is called, or fails if it times out.
354      *
355      * <p>This method is useful on tests that explicitly verifies the connection, but should be
356      * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
357      * where the service might have being disconnected already; for example, if the fill request
358      * was replied with a {@code null} response) - if a text needs to block until the service
359      * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
360      */
waitUntilConnected()361     public static void waitUntilConnected() throws Exception {
362         waitConnectionState(CONNECTION_TIMEOUT, true);
363     }
364 
365     /**
366      * Waits until {@link #onDisconnected()} is called, or fails if it times out.
367      *
368      * <p>This method is useful on tests that explicitly verifies the connection, but should be
369      * avoided in other tests, as it adds extra time to the test execution.
370      */
waitUntilDisconnected()371     public static void waitUntilDisconnected() throws Exception {
372         waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
373     }
374 
waitConnectionState(Timeout timeout, boolean expected)375     private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
376         timeout.run("wait for connected=" + expected,  () -> {
377             return isConnected() == expected ? Boolean.TRUE : null;
378         });
379     }
380 
381     /**
382      * Gets the {@link Replier} singleton.
383      */
getReplier()384     public static Replier getReplier() {
385         return sReplier;
386     }
387 
388     /** Marks that a test is in session */
startTest()389     public static void startTest() {
390         sTestRunning.set(true);
391     }
392 
setAutofillServiceClass(String name)393     public static void setAutofillServiceClass(String name) {
394         sServiceLabel = name;
395     }
396 
resetStaticState()397     public static void resetStaticState() {
398         sTestRunning.set(false);
399         sConnected.set(false);
400         sServiceLabel = SERVICE_CLASS;
401         sSavedDatasetsInfoReplier = null;
402     }
403 
404     /**
405      * POJO representation of the contents of a
406      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
407      * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
408      */
409     public static final class FillRequest {
410         public final AssistStructure structure;
411         public final List<FillContext> contexts;
412         public final Bundle data;
413         public final CancellationSignal cancellationSignal;
414         public final FillCallback callback;
415         public final int flags;
416         public final InlineSuggestionsRequest inlineRequest;
417         public final IntentSender delayFillIntentSender;
418         public final int requestId;
419         public final List<String> hints;
420 
FillRequest(List<FillContext> contexts, List<String> hints, Bundle data, CancellationSignal cancellationSignal, FillCallback callback, int flags, InlineSuggestionsRequest inlineRequest, IntentSender delayFillIntentSender, int requestId)421         private FillRequest(List<FillContext> contexts, List<String> hints, Bundle data,
422                 CancellationSignal cancellationSignal, FillCallback callback, int flags,
423                 InlineSuggestionsRequest inlineRequest,
424                 IntentSender delayFillIntentSender,
425                 int requestId) {
426             this.contexts = contexts;
427             this.hints = hints;
428             this.data = data;
429             this.cancellationSignal = cancellationSignal;
430             this.callback = callback;
431             this.flags = flags;
432             this.structure = contexts.get(contexts.size() - 1).getStructure();
433             this.inlineRequest = inlineRequest;
434             this.delayFillIntentSender = delayFillIntentSender;
435             this.requestId = requestId;
436         }
437 
438         @Override
toString()439         public String toString() {
440             return "FillRequest[activity=" + getActivityName(contexts) + ", flags=" + flags
441                     + ", bundle=" + data + ", structure=" + Helper.toString(structure) + "]";
442         }
443     }
444 
445     /**
446      * POJO representation of the contents of a
447      * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
448      * that can be asserted at the end of a test case.
449      */
450     public static final class SaveRequest {
451         public final List<FillContext> contexts;
452         public final AssistStructure structure;
453         public final Bundle data;
454         public final SaveCallback callback;
455         public final List<String> datasetIds;
456 
SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback, List<String> datasetIds)457         private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
458                 List<String> datasetIds) {
459             if (contexts != null && contexts.size() > 0) {
460                 structure = contexts.get(contexts.size() - 1).getStructure();
461             } else {
462                 structure = null;
463             }
464             this.contexts = contexts;
465             this.data = data;
466             this.callback = callback;
467             this.datasetIds = datasetIds;
468         }
469 
470         @Override
toString()471         public String toString() {
472             return "SaveRequest:" + getActivityName(contexts);
473         }
474     }
475 
476     /**
477      * Object used to answer a
478      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
479      * CancellationSignal, FillCallback)}
480      * on behalf of a unit test method.
481      */
482     public static final class Replier {
483 
484         /*
485          * This is used by the test to skip explicitly calling sReplier.getFillRequest().
486          * This will store a callback that gets called whenever a new Fill Request comes.
487          * This makes the code simpler, as only one step is needed to send FillResponse
488          * and verify FillRequest.
489          *
490          * Before:
491          *   sReplier.addResponse(...)
492          *   triggerFillRequestMechanism()
493          *   fr = sReplier.getFillRequest()
494          *   assert(fr)...isTrue()
495          *
496          * After:
497          *   sReplier.onRequest((fr) -> {
498          *      assert(fr)...isTrue()
499          *      return new FillResponse()
500          *   })
501          *   triggerFillRequestMechanism()
502          **/
503         private final BlockingQueue<Function<FillRequest, CannedFillResponse>> mLazyResponses =
504                 new LinkedBlockingQueue<>();
505         private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
506         private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
507         private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
508 
509         private List<Throwable> mExceptions;
510         private IntentSender mOnSaveIntentSender;
511         private String mAcceptedPackageName;
512 
513         private Handler mHandler;
514 
515         private boolean mReportUnhandledFillRequest = true;
516         private boolean mReportUnhandledSaveRequest = true;
517 
518         private List<FillEventHistory> mFillEventHistory = new ArrayList<>();
519 
520         private @Nullable FillCallback mDelayedCallback = null;
521         private @Nullable FillResponse mDelayedResponse = null;
522         private @Nullable FillRequest mDelayedRequest = null;
523 
Replier()524         private Replier() {
525         }
526 
527         private IdMode mIdMode = IdMode.RESOURCE_ID;
528 
setIdMode(IdMode mode)529         public void setIdMode(IdMode mode) {
530             this.mIdMode = mode;
531         }
532 
acceptRequestsFromPackage(String packageName)533         public void acceptRequestsFromPackage(String packageName) {
534             mAcceptedPackageName = packageName;
535         }
536 
537         /**
538          * Gets the exceptions thrown asynchronously, if any.
539          */
540         @Nullable
getExceptions()541         public List<Throwable> getExceptions() {
542             return mExceptions;
543         }
544 
addException(@ullable Throwable e)545         private void addException(@Nullable Throwable e) {
546             if (e == null) return;
547 
548             if (mExceptions == null) {
549                 mExceptions = new ArrayList<>();
550             }
551             mExceptions.add(e);
552         }
553 
554         /**
555          * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
556          * one {@link Dataset}.
557          */
addResponse(CannedDataset dataset)558         public Replier addResponse(CannedDataset dataset) {
559             return addResponse(new CannedFillResponse.Builder()
560                     .addDataset(dataset)
561                     .build());
562         }
563 
564         /**
565          * Sets the expectation for the next {@code onFillRequest}.
566          */
addResponse(CannedFillResponse response)567         public Replier addResponse(CannedFillResponse response) {
568             if (response == null) {
569                 throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
570             }
571             mResponses.add(response);
572             return this;
573         }
574 
onRequest(Function<FillRequest, CannedFillResponse> lazyResponse)575         public Replier onRequest(Function<FillRequest, CannedFillResponse> lazyResponse) {
576             if (lazyResponse == null) {
577                 throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
578             }
579             mLazyResponses.add(lazyResponse);
580             return this;
581         }
582 
583         /**
584          * Sets the {@link IntentSender} that is passed to
585          * {@link SaveCallback#onSuccess(IntentSender)}.
586          */
setOnSave(IntentSender intentSender)587         public Replier setOnSave(IntentSender intentSender) {
588             mOnSaveIntentSender = intentSender;
589             return this;
590         }
591 
592         /**
593          * Gets the next fill request, in the order received.
594          */
getNextFillRequest()595         public FillRequest getNextFillRequest() {
596             FillRequest request;
597             try {
598                 request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
599             } catch (InterruptedException e) {
600                 Thread.currentThread().interrupt();
601                 throw new IllegalStateException("Interrupted", e);
602             }
603             if (request == null) {
604                 throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
605             }
606             return request;
607         }
608 
609         /** Continue to process the delayed fill request based on the stored values. */
processDelayedResponse()610         public void processDelayedResponse() {
611             if (mDelayedCallback != null && mDelayedResponse != null && mDelayedRequest != null) {
612                 mDelayedCallback.onSuccess(mDelayedResponse);
613                 Log.v(
614                         TAG,
615                         "onFillRequest("
616                                 + mDelayedRequest.requestId
617                                 + "): fillResponse = "
618                                 + mDelayedResponse);
619                 Helper.offer(mFillRequests, mDelayedRequest, CONNECTION_TIMEOUT.ms());
620             }
621             // and then reset all parameters.
622             mDelayedCallback = null;
623             mDelayedResponse = null;
624             mDelayedRequest = null;
625             Log.v(TAG, "processDelayedResponse: all stored values are resetted");
626         }
627 
628         /**
629          * Gets the last FillEventHistory that was return as part of onSessionDestroyed(), for easy
630          * assertion
631          */
getLastFillEventHistory()632         public @Nullable FillEventHistory getLastFillEventHistory() {
633             return getLastFillEventHistory(0);
634         }
635 
636         /**
637          * Gets the last FillEventHistory that was return as part of onSessionDestroyed(), in the
638          * order that it was added.
639          *
640          * @param numFromLast 0 for the most recent, getSessionDestroyedCount() - 1 for the first
641          */
getLastFillEventHistory(int numFromLast)642         public @Nullable FillEventHistory getLastFillEventHistory(int numFromLast) {
643             int index = mFillEventHistory.size() - numFromLast - 1;
644             return mFillEventHistory.get(index);
645         }
646 
647         /**
648          * Used by InstrumentedAutofillService to add the last FillEventHistory returned by
649          * onSessionDestroyed()
650          */
addLastFillEventHistory(@ullable FillEventHistory history)651         public void addLastFillEventHistory(@Nullable FillEventHistory history) {
652             mFillEventHistory.add(history);
653         }
654 
655         /**
656          * @return the amount of onSessionDestroyed() calls the service has received.
657          */
getSessionDestroyedCount()658         public int getSessionDestroyedCount() {
659             return mFillEventHistory.size();
660         }
661 
662         /**
663          * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
664          * was not called.
665          *
666          * <p>Should only be called in cases where it's not expected to be called, as it will
667          * sleep for a few ms.
668          */
assertOnFillRequestNotCalled()669         public void assertOnFillRequestNotCalled() {
670             SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
671             assertThat(mFillRequests).isEmpty();
672         }
673 
674         /**
675          * Asserts all {@link AutofillService#onFillRequest(
676          * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
677          * received by the service were properly {@link #getNextFillRequest() handled} by the test
678          * case.
679          */
assertNoUnhandledFillRequests()680         public void assertNoUnhandledFillRequests() {
681             if (mFillRequests.isEmpty()) return; // Good job, test case!
682 
683             if (!mReportUnhandledFillRequest) {
684                 // Just log, so it's not thrown again on @After if already thrown on main body
685                 Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
686                         + "but logging just in case: " + mFillRequests);
687                 return;
688             }
689 
690             mReportUnhandledFillRequest = false;
691             throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
692                     + mFillRequests);
693         }
694 
695         /**
696          * Gets the current number of unhandled requests.
697          */
getNumberUnhandledFillRequests()698         public int getNumberUnhandledFillRequests() {
699             return mFillRequests.size();
700         }
701 
702         /**
703          * Gets the next save request, in the order received.
704          *
705          * <p>Typically called at the end of a test case, to assert the initial request.
706          */
getNextSaveRequest()707         public SaveRequest getNextSaveRequest() {
708             SaveRequest request;
709             try {
710                 request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
711             } catch (InterruptedException e) {
712                 Thread.currentThread().interrupt();
713                 throw new IllegalStateException("Interrupted", e);
714             }
715             if (request == null) {
716                 throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
717             }
718             return request;
719         }
720 
721         /**
722          * Asserts all
723          * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
724          * save requests} received by the service were properly
725          * {@link #getNextFillRequest() handled} by the test case.
726          */
assertNoUnhandledSaveRequests()727         public void assertNoUnhandledSaveRequests() {
728             if (mSaveRequests.isEmpty()) return; // Good job, test case!
729 
730             if (!mReportUnhandledSaveRequest) {
731                 // Just log, so it's not thrown again on @After if already thrown on main body
732                 Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
733                         + "but logging just in case: " + mSaveRequests);
734                 return;
735             }
736 
737             mReportUnhandledSaveRequest = false;
738             throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
739                     + mSaveRequests);
740         }
741 
setHandler(Handler handler)742         public void setHandler(Handler handler) {
743             mHandler = handler;
744         }
745 
746         /**
747          * Resets its internal state.
748          */
reset()749         public void reset() {
750             mLazyResponses.clear();
751             mResponses.clear();
752             mFillRequests.clear();
753             mSaveRequests.clear();
754             mExceptions = null;
755             mOnSaveIntentSender = null;
756             mAcceptedPackageName = null;
757             mReportUnhandledFillRequest = true;
758             mReportUnhandledSaveRequest = true;
759             mFillEventHistory = new ArrayList<FillEventHistory>();
760         }
761 
onFillRequest(List<FillContext> contexts, List<String> hints, Bundle data, CancellationSignal cancellationSignal, FillCallback callback, int flags, InlineSuggestionsRequest inlineRequest, IntentSender delayFillIntentSender, int requestId)762         private void onFillRequest(List<FillContext> contexts, List<String> hints, Bundle data,
763                 CancellationSignal cancellationSignal, FillCallback callback, int flags,
764                 InlineSuggestionsRequest inlineRequest, IntentSender delayFillIntentSender,
765                 int requestId) {
766             boolean hasLazyResponse = mLazyResponses.size() > 0;
767             try {
768                 CannedFillResponse response = null;
769                 try {
770                     if (hasLazyResponse) {
771                         response =
772                                 mLazyResponses
773                                         .poll()
774                                         .apply(
775                                                 new FillRequest(
776                                                         contexts,
777                                                         hints,
778                                                         data,
779                                                         cancellationSignal,
780                                                         callback,
781                                                         flags,
782                                                         inlineRequest,
783                                                         delayFillIntentSender,
784                                                         requestId));
785                     } else {
786                         response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
787                     }
788                 } catch (InterruptedException e) {
789                     Log.w(TAG, "Interrupted getting CannedResponse: " + e);
790                     Thread.currentThread().interrupt();
791                     addException(e);
792                     return;
793                 }
794                 if (response == null) {
795                     final String activityName = getActivityName(contexts);
796                     final String msg = "onFillRequest() for activity " + activityName
797                             + " received when no canned response was set.";
798                     dumpStructure(msg, contexts);
799                     return;
800                 }
801                 if (response.getResponseType() == NULL) {
802                     Log.d(TAG, "onFillRequest(): replying with null");
803                     callback.onSuccess(null);
804                     return;
805                 }
806 
807                 if (response.getResponseType() == TIMEOUT) {
808                     Log.d(TAG, "onFillRequest(): not replying at all");
809                     return;
810                 }
811 
812                 if (response.getResponseType() == FAILURE) {
813                     Log.d(TAG, "onFillRequest(): replying with failure");
814                     callback.onFailure("D'OH!");
815                     return;
816                 }
817 
818                 if (response.getResponseType() == ResponseType.NO_MORE) {
819                     Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
820                     addException(new IllegalStateException("got unexpected request"));
821                     callback.onSuccess(null);
822                     return;
823                 }
824 
825                 final String failureMessage = response.getFailureMessage();
826                 if (failureMessage != null) {
827                     Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
828                     callback.onFailure(failureMessage);
829                     return;
830                 }
831 
832                 final FillResponse fillResponse;
833 
834                 switch (mIdMode) {
835                     case RESOURCE_ID:
836                         fillResponse = response.asFillResponse(contexts,
837                                 (id) -> Helper.findNodeByResourceId(contexts, id));
838                         break;
839                     case HTML_NAME:
840                         fillResponse = response.asFillResponse(contexts,
841                                 (name) -> Helper.findNodeByHtmlName(contexts, name));
842                         break;
843                     case HTML_NAME_OR_RESOURCE_ID:
844                         fillResponse = response.asFillResponse(contexts,
845                                 (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
846                         break;
847                     case PCC_ID:
848                         // TODO: SaveInfo undetermined for PCC
849                         fillResponse = response.asPccFillResponse(contexts,
850                                 (id) -> Helper.findNodeByResourceId(contexts, id));
851                         break;
852                     default:
853                         throw new IllegalStateException("Unknown id mode: " + mIdMode);
854                 }
855 
856                 if (response.getResponseType() == ResponseType.DELAY) {
857                     mDelayedCallback = callback;
858                     mDelayedResponse = fillResponse;
859                     mDelayedRequest =
860                             new FillRequest(
861                                     contexts,
862                                     hints,
863                                     data,
864                                     cancellationSignal,
865                                     callback,
866                                     flags,
867                                     inlineRequest,
868                                     delayFillIntentSender,
869                                     requestId);
870                 } else {
871                     Log.v(TAG, "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
872                     callback.onSuccess(fillResponse);
873                 }
874             } catch (Throwable t) {
875                 addException(t);
876             } finally {
877                 if (!hasLazyResponse) {
878                     Helper.offer(mFillRequests, new FillRequest(contexts, hints, data,
879                             cancellationSignal, callback, flags, inlineRequest,
880                             delayFillIntentSender, requestId),
881                             CONNECTION_TIMEOUT.ms());
882                 }
883             }
884         }
885 
onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback, List<String> datasetIds)886         private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
887                 List<String> datasetIds) {
888             Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
889 
890             try {
891                 if (mOnSaveIntentSender != null) {
892                     callback.onSuccess(mOnSaveIntentSender);
893                 } else {
894                     callback.onSuccess();
895                 }
896             } finally {
897                 Helper.offer(mSaveRequests, new SaveRequest(contexts, data, callback, datasetIds),
898                         CONNECTION_TIMEOUT.ms());
899             }
900         }
901 
dump(PrintWriter pw)902         private void dump(PrintWriter pw) {
903             pw.print("mResponses: "); pw.println(mResponses);
904             pw.print("mFillRequests: "); pw.println(mFillRequests);
905             pw.print("mSaveRequests: "); pw.println(mSaveRequests);
906             pw.print("mExceptions: "); pw.println(mExceptions);
907             pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
908             pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
909             pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
910             pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
911             pw.print("mIdMode: "); pw.println(mIdMode);
912             pw.print("mFillEventHistory: "); pw.println(mFillEventHistory);
913         }
914     }
915 }