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