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