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