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