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