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