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