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