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