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