Session.java revision 0171840cebbb6a3dc90a67081e83fc94ea5fe2d3
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 17package com.android.server.autofill; 18 19import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 20import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; 21import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS; 22import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; 23import static android.view.autofill.AutofillManager.ACTION_START_SESSION; 24import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; 25import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; 26import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; 27 28import static com.android.server.autofill.Helper.sDebug; 29import static com.android.server.autofill.Helper.sPartitionMaxCount; 30import static com.android.server.autofill.Helper.sVerbose; 31import static com.android.server.autofill.Helper.toArray; 32import static com.android.server.autofill.ViewState.STATE_AUTOFILLED; 33import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION; 34 35import android.annotation.NonNull; 36import android.annotation.Nullable; 37import android.app.Activity; 38import android.app.ActivityManager; 39import android.app.assist.AssistStructure; 40import android.app.assist.AssistStructure.AutofillOverlay; 41import android.app.assist.AssistStructure.ViewNode; 42import android.content.ComponentName; 43import android.content.Context; 44import android.content.Intent; 45import android.content.IntentSender; 46import android.graphics.Rect; 47import android.metrics.LogMaker; 48import android.os.Binder; 49import android.os.Bundle; 50import android.os.IBinder; 51import android.os.Parcelable; 52import android.os.RemoteException; 53import android.service.autofill.AutofillService; 54import android.service.autofill.Dataset; 55import android.service.autofill.FillContext; 56import android.service.autofill.FillRequest; 57import android.service.autofill.FillResponse; 58import android.service.autofill.SaveInfo; 59import android.service.autofill.SaveRequest; 60import android.util.ArrayMap; 61import android.util.ArraySet; 62import android.util.Slog; 63import android.util.SparseArray; 64import android.view.autofill.AutofillId; 65import android.view.autofill.AutofillManager; 66import android.view.autofill.AutofillValue; 67import android.view.autofill.IAutoFillManagerClient; 68import android.view.autofill.IAutofillWindowPresenter; 69 70import com.android.internal.R; 71import com.android.internal.annotations.GuardedBy; 72import com.android.internal.logging.MetricsLogger; 73import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 74import com.android.internal.os.HandlerCaller; 75import com.android.internal.os.IResultReceiver; 76import com.android.internal.util.ArrayUtils; 77import com.android.server.autofill.ui.AutoFillUI; 78 79import java.io.PrintWriter; 80import java.util.ArrayList; 81import java.util.Collections; 82import java.util.List; 83import java.util.Map; 84import java.util.concurrent.atomic.AtomicInteger; 85 86/** 87 * A session for a given activity. 88 * 89 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track 90 * of the current {@link ViewState} to display the appropriate UI. 91 * 92 * <p>Although the autofill requests and callbacks are stateless from the service's point of 93 * view, we need to keep state in the framework side for cases such as authentication. For 94 * example, when service return a {@link FillResponse} that contains all the fields needed 95 * to fill the activity but it requires authentication first, that response need to be held 96 * until the user authenticates or it times out. 97 */ 98final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, 99 AutoFillUI.AutoFillUiCallback { 100 private static final String TAG = "AutofillSession"; 101 102 private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; 103 104 private final AutofillManagerServiceImpl mService; 105 private final HandlerCaller mHandlerCaller; 106 private final Object mLock; 107 private final AutoFillUI mUi; 108 109 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 110 111 private static AtomicInteger sIdCounter = new AtomicInteger(); 112 113 /** Id of the session */ 114 public final int id; 115 116 /** uid the session is for */ 117 public final int uid; 118 119 @GuardedBy("mLock") 120 @NonNull private IBinder mActivityToken; 121 122 /** Package name of the app that is auto-filled */ 123 @NonNull private final String mPackageName; 124 125 @GuardedBy("mLock") 126 private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); 127 128 /** 129 * Id of the View currently being displayed. 130 */ 131 @GuardedBy("mLock") 132 @Nullable private AutofillId mCurrentViewId; 133 134 @GuardedBy("mLock") 135 private IAutoFillManagerClient mClient; 136 137 private final RemoteFillService mRemoteFillService; 138 139 @GuardedBy("mLock") 140 private SparseArray<FillResponse> mResponses; 141 142 /** 143 * Contexts read from the app; they will be updated (sanitized, change values for save) before 144 * sent to {@link AutofillService}. Ordered by the time they we read. 145 */ 146 @GuardedBy("mLock") 147 private ArrayList<FillContext> mContexts; 148 149 /** 150 * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. 151 */ 152 private boolean mHasCallback; 153 154 /** 155 * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved 156 * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls. 157 */ 158 @GuardedBy("mLock") 159 private Bundle mClientState; 160 161 @GuardedBy("mLock") 162 private boolean mDestroyed; 163 164 /** Whether the session is currently saving */ 165 @GuardedBy("mLock") 166 private boolean mIsSaving; 167 168 169 /** 170 * Receiver of assist data from the app's {@link Activity}. 171 */ 172 private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { 173 @Override 174 public void send(int resultCode, Bundle resultData) throws RemoteException { 175 final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE); 176 if (structure == null) { 177 Slog.wtf(TAG, "no assist structure"); 178 return; 179 } 180 181 final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS); 182 if (receiverExtras == null) { 183 Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver"); 184 return; 185 } 186 187 final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); 188 189 if (sVerbose) { 190 Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure); 191 } 192 193 final FillRequest request; 194 synchronized (mLock) { 195 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(), 196 // even if if the activity is gone by then, but structure .ensureData() gives a 197 // ONE_WAY warning because system_service could block on app calls. We need to 198 // change AssistStructure so it provides a "one-way" writeToParcel() method that 199 // sends all the data 200 structure.ensureData(); 201 202 // Sanitize structure before it's sent to service. 203 structure.sanitizeForParceling(true); 204 205 // Flags used to start the session. 206 final int flags = structure.getFlags(); 207 208 if (mContexts == null) { 209 mContexts = new ArrayList<>(1); 210 } 211 mContexts.add(new FillContext(requestId, structure)); 212 213 cancelCurrentRequestLocked(); 214 215 final int numContexts = mContexts.size(); 216 for (int i = 0; i < numContexts; i++) { 217 fillContextWithAllowedValues(mContexts.get(i), flags); 218 } 219 220 request = new FillRequest(requestId, mContexts, mClientState, flags); 221 } 222 223 mRemoteFillService.onFillRequest(request); 224 } 225 }; 226 227 /** 228 * Returns the ids of all entries in {@link #mViewStates} in the same order. 229 */ 230 private AutofillId[] getIdsOfAllViewStates() { 231 final int numViewState = mViewStates.size(); 232 final AutofillId[] ids = new AutofillId[numViewState]; 233 for (int i = 0; i < numViewState; i++) { 234 ids[i] = mViewStates.valueAt(i).id; 235 } 236 237 return ids; 238 } 239 240 /** 241 * Updates values of the nodes in the context's structure so that: 242 * - proper node is focused 243 * - autofillValue is sent back to service when it was previously autofilled 244 * - autofillValue is sent in the view used to force a request 245 * 246 * @param fillContext The context to be filled 247 * @param flags The flags that started the session 248 */ 249 private void fillContextWithAllowedValues(@NonNull FillContext fillContext, int flags) { 250 final ViewNode[] nodes = fillContext.findViewNodesByAutofillIds(getIdsOfAllViewStates()); 251 252 final int numViewState = mViewStates.size(); 253 for (int i = 0; i < numViewState; i++) { 254 final ViewState viewState = mViewStates.valueAt(i); 255 256 final ViewNode node = nodes[i]; 257 if (node == null) { 258 Slog.w(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id); 259 continue; 260 } 261 262 final AutofillValue currentValue = viewState.getCurrentValue(); 263 final AutofillValue filledValue = viewState.getAutofilledValue(); 264 final AutofillOverlay overlay = new AutofillOverlay(); 265 266 // Sanitizes the value if the current value matches what the service sent. 267 if (filledValue != null && filledValue.equals(currentValue)) { 268 overlay.value = currentValue; 269 } 270 271 if (mCurrentViewId != null) { 272 // Updates the focus value. 273 overlay.focused = mCurrentViewId.equals(viewState.id); 274 // Sanitizes the value of the focused field in a manual request. 275 if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) { 276 overlay.value = currentValue; 277 } 278 } 279 node.setAutofillOverlay(overlay); 280 } 281 } 282 283 /** 284 * Cancels the last request sent to the {@link #mRemoteFillService}. 285 */ 286 private void cancelCurrentRequestLocked() { 287 int canceledRequest = mRemoteFillService.cancelCurrentRequest(); 288 289 // Remove the FillContext as there will never be a response for the service 290 if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { 291 int numContexts = mContexts.size(); 292 293 // It is most likely the last context, hence search backwards 294 for (int i = numContexts - 1; i >= 0; i--) { 295 if (mContexts.get(i).getRequestId() == canceledRequest) { 296 mContexts.remove(i); 297 break; 298 } 299 } 300 } 301 302 } 303 304 /** 305 * Reads a new structure and then request a new fill response from the fill service. 306 */ 307 private void requestNewFillResponseLocked(int flags) { 308 int requestId; 309 310 do { 311 requestId = sIdCounter.getAndIncrement(); 312 } while (requestId == INVALID_REQUEST_ID); 313 314 if (sVerbose) { 315 Slog.v(TAG, "Requesting structure for requestId=" + requestId + ", flags=" + flags); 316 } 317 318 // If the focus changes very quickly before the first request is returned each focus change 319 // triggers a new partition and we end up with many duplicate partitions. This is 320 // enhanced as the focus change can be much faster than the taking of the assist structure. 321 // Hence remove the currently queued request and replace it with the one queued after the 322 // structure is taken. This causes only one fill request per bust of focus changes. 323 cancelCurrentRequestLocked(); 324 325 try { 326 final Bundle receiverExtras = new Bundle(); 327 receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); 328 final long identity = Binder.clearCallingIdentity(); 329 try { 330 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver, 331 receiverExtras, mActivityToken, flags)) { 332 Slog.w(TAG, "failed to request autofill data for " + mActivityToken); 333 } 334 } finally { 335 Binder.restoreCallingIdentity(identity); 336 } 337 } catch (RemoteException e) { 338 // Should not happen, it's a local call. 339 } 340 } 341 342 Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, 343 @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId, 344 @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, 345 @NonNull IBinder client, boolean hasCallback, 346 @NonNull ComponentName componentName, @NonNull String packageName) { 347 id = sessionId; 348 this.uid = uid; 349 mService = service; 350 mLock = lock; 351 mUi = ui; 352 mHandlerCaller = handlerCaller; 353 mRemoteFillService = new RemoteFillService(context, componentName, userId, this); 354 mActivityToken = activityToken; 355 mHasCallback = hasCallback; 356 mPackageName = packageName; 357 mClient = IAutoFillManagerClient.Stub.asInterface(client); 358 359 mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName); 360 } 361 362 /** 363 * Gets the currently registered activity token 364 * 365 * @return The activity token 366 */ 367 @NonNull IBinder getActivityTokenLocked() { 368 return mActivityToken; 369 } 370 371 /** 372 * Sets new activity and client for this session. 373 * 374 * @param newActivity The token of the new activity 375 * @param newClient The client receiving autofill callbacks 376 */ 377 void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { 378 synchronized (mLock) { 379 if (mDestroyed) { 380 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: " 381 + id + " destroyed"); 382 return; 383 } 384 mActivityToken = newActivity; 385 mClient = IAutoFillManagerClient.Stub.asInterface(newClient); 386 387 // The tracked id are not persisted in the client, hence update them 388 updateTrackedIdsLocked(); 389 } 390 } 391 392 // FillServiceCallbacks 393 @Override 394 public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response, 395 int serviceUid, @NonNull String servicePackageName) { 396 synchronized (mLock) { 397 if (mDestroyed) { 398 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " 399 + id + " destroyed"); 400 return; 401 } 402 } 403 if (response == null) { 404 if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null"); 405 if ((requestFlags & FLAG_MANUAL_REQUEST) != 0) { 406 getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this); 407 } 408 // Nothing to be done, but need to notify client. 409 notifyUnavailableToClient(); 410 removeSelf(); 411 return; 412 } 413 414 mService.setLastResponse(serviceUid, response); 415 416 if ((response.getDatasets() == null || response.getDatasets().isEmpty()) 417 && response.getAuthentication() == null) { 418 // Response is "empty" from an UI point of view, need to notify client. 419 notifyUnavailableToClient(); 420 } 421 synchronized (mLock) { 422 processResponseLocked(response); 423 } 424 425 final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) 426 .setType(MetricsEvent.TYPE_SUCCESS) 427 .setPackageName(mPackageName) 428 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, 429 response.getDatasets() == null ? 0 : response.getDatasets().size()) 430 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, 431 servicePackageName); 432 mMetricsLogger.write(log); 433 } 434 435 // FillServiceCallbacks 436 @Override 437 public void onFillRequestFailure(@Nullable CharSequence message, 438 @NonNull String servicePackageName) { 439 synchronized (mLock) { 440 if (mDestroyed) { 441 Slog.w(TAG, "Call to Session#onFillRequestFailure() rejected - session: " 442 + id + " destroyed"); 443 return; 444 } 445 } 446 LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) 447 .setType(MetricsEvent.TYPE_FAILURE) 448 .setPackageName(mPackageName) 449 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName); 450 mMetricsLogger.write(log); 451 452 getUiForShowing().showError(message, this); 453 removeSelf(); 454 } 455 456 // FillServiceCallbacks 457 @Override 458 public void onSaveRequestSuccess(@NonNull String servicePackageName) { 459 synchronized (mLock) { 460 mIsSaving = false; 461 462 if (mDestroyed) { 463 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " 464 + id + " destroyed"); 465 return; 466 } 467 } 468 LogMaker log = (new LogMaker( 469 MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST)) 470 .setType(MetricsEvent.TYPE_SUCCESS) 471 .setPackageName(mPackageName) 472 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName); 473 mMetricsLogger.write(log); 474 475 // Nothing left to do... 476 removeSelf(); 477 } 478 479 // FillServiceCallbacks 480 @Override 481 public void onSaveRequestFailure(@Nullable CharSequence message, 482 @NonNull String servicePackageName) { 483 synchronized (mLock) { 484 mIsSaving = false; 485 486 if (mDestroyed) { 487 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " 488 + id + " destroyed"); 489 return; 490 } 491 } 492 LogMaker log = (new LogMaker( 493 MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST)) 494 .setType(MetricsEvent.TYPE_FAILURE) 495 .setPackageName(mPackageName) 496 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName); 497 mMetricsLogger.write(log); 498 499 getUiForShowing().showError(message, this); 500 removeSelf(); 501 } 502 503 /** 504 * Gets the {@link FillContext} for a request. 505 * 506 * @param requestId The id of the request 507 * 508 * @return The context or {@code null} if there is no context 509 */ 510 @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) { 511 if (mContexts == null) { 512 return null; 513 } 514 515 int numContexts = mContexts.size(); 516 for (int i = 0; i < numContexts; i++) { 517 FillContext context = mContexts.get(i); 518 519 if (context.getRequestId() == requestId) { 520 return context; 521 } 522 } 523 524 return null; 525 } 526 527 // FillServiceCallbacks 528 @Override 529 public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) { 530 final Intent fillInIntent; 531 synchronized (mLock) { 532 synchronized (mLock) { 533 if (mDestroyed) { 534 Slog.w(TAG, "Call to Session#authenticate() rejected - session: " 535 + id + " destroyed"); 536 return; 537 } 538 } 539 fillInIntent = createAuthFillInIntent( 540 getFillContextByRequestIdLocked(requestId).getStructure(), extras); 541 } 542 543 mService.setAuthenticationSelected(); 544 545 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); 546 mHandlerCaller.getHandler().post(() -> startAuthentication(authenticationId, 547 intent, fillInIntent)); 548 } 549 550 // FillServiceCallbacks 551 @Override 552 public void onServiceDied(RemoteFillService service) { 553 // TODO(b/337565347): implement 554 } 555 556 // AutoFillUiCallback 557 @Override 558 public void fill(int requestId, int datasetIndex, Dataset dataset) { 559 synchronized (mLock) { 560 if (mDestroyed) { 561 Slog.w(TAG, "Call to Session#fill() rejected - session: " 562 + id + " destroyed"); 563 return; 564 } 565 } 566 mHandlerCaller.getHandler().post(() -> autoFill(requestId, datasetIndex, dataset)); 567 } 568 569 // AutoFillUiCallback 570 @Override 571 public void save() { 572 synchronized (mLock) { 573 if (mDestroyed) { 574 Slog.w(TAG, "Call to Session#save() rejected - session: " 575 + id + " destroyed"); 576 return; 577 } 578 } 579 mHandlerCaller.getHandler() 580 .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, id, 0) 581 .sendToTarget(); 582 } 583 584 // AutoFillUiCallback 585 @Override 586 public void cancelSave() { 587 synchronized (mLock) { 588 mIsSaving = false; 589 590 if (mDestroyed) { 591 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " 592 + id + " destroyed"); 593 return; 594 } 595 } 596 mHandlerCaller.getHandler().post(() -> removeSelf()); 597 } 598 599 // AutoFillUiCallback 600 @Override 601 public void requestShowFillUi(AutofillId id, int width, int height, 602 IAutofillWindowPresenter presenter) { 603 synchronized (mLock) { 604 if (mDestroyed) { 605 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: " 606 + id + " destroyed"); 607 return; 608 } 609 if (id.equals(mCurrentViewId)) { 610 try { 611 final ViewState view = mViewStates.get(id); 612 mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(), 613 presenter); 614 } catch (RemoteException e) { 615 Slog.e(TAG, "Error requesting to show fill UI", e); 616 } 617 } else { 618 if (sDebug) { 619 Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" 620 + mCurrentViewId + ") anymore"); 621 } 622 } 623 } 624 } 625 626 // AutoFillUiCallback 627 @Override 628 public void requestHideFillUi(AutofillId id) { 629 synchronized (mLock) { 630 // NOTE: We allow this call in a destroyed state as the UI is 631 // asked to go away after we get destroyed, so let it do that. 632 try { 633 mClient.requestHideFillUi(this.id, id); 634 } catch (RemoteException e) { 635 Slog.e(TAG, "Error requesting to hide fill UI", e); 636 } 637 } 638 } 639 640 // AutoFillUiCallback 641 @Override 642 public void startIntentSender(IntentSender intentSender) { 643 synchronized (mLock) { 644 if (mDestroyed) { 645 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: " 646 + id + " destroyed"); 647 return; 648 } 649 removeSelfLocked(); 650 } 651 mHandlerCaller.getHandler().post(() -> { 652 try { 653 synchronized (mLock) { 654 mClient.startIntentSender(intentSender); 655 } 656 } catch (RemoteException e) { 657 Slog.e(TAG, "Error launching auth intent", e); 658 } 659 }); 660 } 661 662 void setAuthenticationResultLocked(Bundle data, int authenticationId) { 663 if (mDestroyed) { 664 Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " 665 + id + " destroyed"); 666 return; 667 } 668 669 final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); 670 final FillResponse authenticatedResponse = mResponses.get(requestId); 671 if (authenticatedResponse == null || data == null) { 672 removeSelf(); 673 return; 674 } 675 676 final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId( 677 authenticationId); 678 // Authenticated a dataset - reset view state regardless if we got a response or a dataset 679 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { 680 final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx); 681 if (dataset == null) { 682 removeSelf(); 683 return; 684 } 685 } 686 687 final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); 688 if (result instanceof FillResponse) { 689 final FillResponse response = (FillResponse) result; 690 mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName); 691 replaceResponseLocked(authenticatedResponse, response); 692 } else if (result instanceof Dataset) { 693 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { 694 final Dataset dataset = (Dataset) result; 695 authenticatedResponse.getDatasets().set(datasetIdx, dataset); 696 autoFill(requestId, datasetIdx, dataset); 697 } 698 } 699 } 700 701 void setHasCallbackLocked(boolean hasIt) { 702 if (mDestroyed) { 703 Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " 704 + id + " destroyed"); 705 return; 706 } 707 mHasCallback = hasIt; 708 } 709 710 /** 711 * Shows the save UI, when session can be saved. 712 * 713 * @return {@code true} if session is done, or {@code false} if it's pending user action. 714 */ 715 public boolean showSaveLocked() { 716 if (mDestroyed) { 717 Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: " 718 + id + " destroyed"); 719 return false; 720 } 721 if (mContexts == null) { 722 Slog.d(TAG, "showSaveLocked(): no contexts"); 723 return true; 724 } 725 if (mResponses == null) { 726 // Happens when the activity / session was finished before the service replied, or 727 // when the service cannot autofill it (and returned a null response). 728 if (sVerbose) { 729 Slog.v(TAG, "showSaveLocked(): no responses on session"); 730 } 731 return true; 732 } 733 734 final int lastResponseIdx = getLastResponseIndex(); 735 if (lastResponseIdx < 0) { 736 Slog.w(TAG, "showSaveLocked(): did not get last response. mResponses=" + mResponses 737 + ", mViewStates=" + mViewStates); 738 return true; 739 } 740 741 final FillResponse response = mResponses.valueAt(lastResponseIdx); 742 final SaveInfo saveInfo = response.getSaveInfo(); 743 if (sVerbose) { 744 Slog.v(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts 745 + ", mViewStates=" + mViewStates); 746 } 747 748 /* 749 * The Save dialog is only shown if all conditions below are met: 750 * 751 * - saveInfo is not null 752 * - autofillValue of all required ids is not null 753 * - autofillValue of at least one id (required or optional) has changed. 754 */ 755 756 if (saveInfo == null) { 757 return true; 758 } 759 760 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 761 if (requiredIds == null || requiredIds.length == 0) { 762 Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo"); 763 return true; 764 } 765 766 boolean allRequiredAreNotEmpty = true; 767 boolean atLeastOneChanged = false; 768 for (int i = 0; i < requiredIds.length; i++) { 769 final AutofillId id = requiredIds[i]; 770 final ViewState viewState = mViewStates.get(id); 771 if (viewState == null) { 772 Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id); 773 allRequiredAreNotEmpty = false; 774 break; 775 } 776 777 final AutofillValue currentValue = viewState.getCurrentValue(); 778 if (currentValue == null || currentValue.isEmpty()) { 779 if (sDebug) { 780 Slog.d(TAG, "showSaveLocked(): empty value for required " + id ); 781 } 782 allRequiredAreNotEmpty = false; 783 break; 784 } 785 final AutofillValue filledValue = viewState.getAutofilledValue(); 786 787 if (!currentValue.equals(filledValue)) { 788 if (sDebug) { 789 Slog.d(TAG, "showSaveLocked(): found a change on required " + id + ": " 790 + filledValue + " => " + currentValue); 791 } 792 atLeastOneChanged = true; 793 } 794 } 795 796 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 797 if (allRequiredAreNotEmpty) { 798 if (!atLeastOneChanged && optionalIds != null) { 799 // No change on required ids yet, look for changes on optional ids. 800 for (int i = 0; i < optionalIds.length; i++) { 801 final AutofillId id = optionalIds[i]; 802 final ViewState viewState = mViewStates.get(id); 803 if (viewState == null) { 804 Slog.w(TAG, "showSaveLocked(): no ViewState for optional " + id); 805 continue; 806 } 807 if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { 808 final AutofillValue currentValue = viewState.getCurrentValue(); 809 final AutofillValue filledValue = viewState.getAutofilledValue(); 810 if (currentValue != null && !currentValue.equals(filledValue)) { 811 if (sDebug) { 812 Slog.d(TAG, "finishSessionLocked(): found a change on optional " 813 + id + ": " + filledValue + " => " + currentValue); 814 } 815 atLeastOneChanged = true; 816 break; 817 } 818 } 819 } 820 } 821 if (atLeastOneChanged) { 822 if (sDebug) Slog.d(TAG, "at least one field changed - showing save UI"); 823 mService.setSaveShown(); 824 getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName, 825 this); 826 827 mIsSaving = true; 828 return false; 829 } 830 } 831 // Nothing changed... 832 if (sDebug) { 833 Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities." 834 + "allRequiredAreNotNull=" + allRequiredAreNotEmpty 835 + ", atLeastOneChanged=" + atLeastOneChanged); 836 } 837 return true; 838 } 839 840 /** 841 * Returns whether the session is currently showing the save UI 842 */ 843 boolean isSavingLocked() { 844 return mIsSaving; 845 } 846 847 /** 848 * Calls service when user requested save. 849 */ 850 void callSaveLocked() { 851 if (mDestroyed) { 852 Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " 853 + id + " destroyed"); 854 return; 855 } 856 857 if (sVerbose) Slog.v(TAG, "callSaveLocked(): mViewStates=" + mViewStates); 858 859 final int numContexts = mContexts.size(); 860 861 for (int contextNum = 0; contextNum < numContexts; contextNum++) { 862 final FillContext context = mContexts.get(contextNum); 863 864 final ViewNode[] nodes = context.findViewNodesByAutofillIds(getIdsOfAllViewStates()); 865 866 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context); 867 868 for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) { 869 final ViewState state = mViewStates.valueAt(viewStateNum); 870 871 final AutofillId id = state.id; 872 final AutofillValue value = state.getCurrentValue(); 873 if (value == null) { 874 if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id); 875 continue; 876 } 877 final ViewNode node = nodes[viewStateNum]; 878 if (node == null) { 879 Slog.w(TAG, "callSaveLocked(): did not find node with id " + id); 880 continue; 881 } 882 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value); 883 884 node.updateAutofillValue(value); 885 } 886 887 // Sanitize structure before it's sent to service. 888 context.getStructure().sanitizeForParceling(false); 889 890 if (sVerbose) { 891 Slog.v(TAG, "Dumping structure of " + context + " before calling service.save()"); 892 context.getStructure().dump(false); 893 } 894 } 895 896 // Remove pending fill requests as the session is finished. 897 cancelCurrentRequestLocked(); 898 899 final SaveRequest saveRequest = new SaveRequest(mContexts, mClientState); 900 mRemoteFillService.onSaveRequest(saveRequest); 901 } 902 903 /** 904 * Starts (if necessary) a new fill request upon entering a view. 905 * 906 * <p>A new request will be started in 2 scenarios: 907 * <ol> 908 * <li>If the user manually requested autofill after the view was already filled. 909 * <li>If the view is part of a new partition. 910 * </ol> 911 * 912 * @param id The id of the view that is entered. 913 * @param viewState The view that is entered. 914 * @param flags The flag that was passed by the AutofillManager. 915 */ 916 private void requestNewFillResponseIfNecessaryLocked(@NonNull AutofillId id, 917 @NonNull ViewState viewState, int flags) { 918 // First check if this is a manual request after view was autofilled. 919 final int state = viewState.getState(); 920 final boolean restart = (state & STATE_AUTOFILLED) != 0 921 && (flags & FLAG_MANUAL_REQUEST) != 0; 922 if (restart) { 923 if (sDebug) Slog.d(TAG, "Re-starting session on view " + id); 924 viewState.setState(STATE_RESTARTED_SESSION); 925 requestNewFillResponseLocked(flags); 926 return; 927 } 928 929 // If it's not, then check if it it should start a partition. 930 if (shouldStartNewPartitionLocked(id)) { 931 if (sDebug) { 932 Slog.d(TAG, "Starting partition for view id " + id + ": " 933 + viewState.getStateAsString()); 934 } 935 viewState.setState(ViewState.STATE_STARTED_PARTITION); 936 requestNewFillResponseLocked(flags); 937 } 938 } 939 940 /** 941 * Determines if a new partition should be started for an id. 942 * 943 * @param id The id of the view that is entered 944 * 945 * @return {@code true} iff a new partition should be started 946 */ 947 private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { 948 if (mResponses == null) { 949 return true; 950 } 951 952 final int numResponses = mResponses.size(); 953 if (numResponses >= sPartitionMaxCount) { 954 Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id 955 + " reached maximum of " + sPartitionMaxCount); 956 return false; 957 } 958 959 for (int responseNum = 0; responseNum < numResponses; responseNum++) { 960 final FillResponse response = mResponses.valueAt(responseNum); 961 962 if (ArrayUtils.contains(response.getIgnoredIds(), id)) { 963 return false; 964 } 965 966 final SaveInfo saveInfo = response.getSaveInfo(); 967 if (saveInfo != null) { 968 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id) 969 || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) { 970 return false; 971 } 972 } 973 974 final ArrayList<Dataset> datasets = response.getDatasets(); 975 if (datasets != null) { 976 final int numDatasets = datasets.size(); 977 978 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) { 979 final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds(); 980 981 if (fields != null && fields.contains(id)) { 982 return false; 983 } 984 } 985 } 986 987 if (ArrayUtils.contains(response.getAuthenticationIds(), id)) { 988 return false; 989 } 990 } 991 992 return true; 993 } 994 995 void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, 996 int flags) { 997 if (mDestroyed) { 998 Slog.w(TAG, "Call to Session#updateLocked() rejected - session: " 999 + id + " destroyed"); 1000 return; 1001 } 1002 if (sVerbose) { 1003 Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + action + ", flags=" + flags); 1004 } 1005 ViewState viewState = mViewStates.get(id); 1006 1007 if (viewState == null) { 1008 if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED 1009 || action == ACTION_VIEW_ENTERED) { 1010 if (sVerbose) Slog.v(TAG, "Creating viewState for " + id + " on " + action); 1011 boolean isIgnored = isIgnoredLocked(id); 1012 viewState = new ViewState(this, id, value, this, 1013 isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL); 1014 mViewStates.put(id, viewState); 1015 if (isIgnored) { 1016 if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + id); 1017 return; 1018 } 1019 } else { 1020 if (sVerbose) Slog.v(TAG, "Ignored action " + action + " for " + id); 1021 return; 1022 } 1023 } 1024 1025 switch(action) { 1026 case ACTION_START_SESSION: 1027 // View is triggering autofill. 1028 mCurrentViewId = viewState.id; 1029 viewState.update(value, virtualBounds); 1030 viewState.setState(ViewState.STATE_STARTED_SESSION); 1031 requestNewFillResponseLocked(flags); 1032 break; 1033 case ACTION_VALUE_CHANGED: 1034 if (value != null && !value.equals(viewState.getCurrentValue())) { 1035 // Always update the internal state. 1036 viewState.setCurrentValue(value); 1037 1038 // Must check if this update was caused by autofilling the view, in which 1039 // case we just update the value, but not the UI. 1040 final AutofillValue filledValue = viewState.getAutofilledValue(); 1041 if (value.equals(filledValue)) { 1042 return; 1043 } 1044 // Update the internal state... 1045 viewState.setState(ViewState.STATE_CHANGED); 1046 1047 //..and the UI 1048 if (value.isText()) { 1049 getUiForShowing().filterFillUi(value.getTextValue().toString(), this); 1050 } else { 1051 getUiForShowing().filterFillUi(null, this); 1052 } 1053 } 1054 break; 1055 case ACTION_VIEW_ENTERED: 1056 if (sVerbose && virtualBounds != null) { 1057 Slog.w(TAG, "entered on virtual child " + id + ": " + virtualBounds); 1058 } 1059 requestNewFillResponseIfNecessaryLocked(id, viewState, flags); 1060 1061 // Remove the UI if the ViewState has changed. 1062 if (mCurrentViewId != viewState.id) { 1063 hideFillUiIfOwnedByMe(); 1064 mCurrentViewId = viewState.id; 1065 } 1066 1067 // If the ViewState is ready to be displayed, onReady() will be called. 1068 viewState.update(value, virtualBounds); 1069 break; 1070 case ACTION_VIEW_EXITED: 1071 if (mCurrentViewId == viewState.id) { 1072 if (sVerbose) Slog.d(TAG, "Exiting view " + id); 1073 hideFillUiIfOwnedByMe(); 1074 mCurrentViewId = null; 1075 } 1076 break; 1077 default: 1078 Slog.w(TAG, "updateLocked(): unknown action: " + action); 1079 } 1080 } 1081 1082 /** 1083 * Checks whether a view should be ignored. 1084 */ 1085 private boolean isIgnoredLocked(AutofillId id) { 1086 if (mResponses == null || mResponses.size() == 0) { 1087 return false; 1088 } 1089 // Always check the latest response only 1090 final FillResponse response = mResponses.valueAt(mResponses.size() - 1); 1091 return ArrayUtils.contains(response.getIgnoredIds(), id); 1092 } 1093 1094 @Override 1095 public void onFillReady(FillResponse response, AutofillId filledId, 1096 @Nullable AutofillValue value) { 1097 synchronized (mLock) { 1098 if (mDestroyed) { 1099 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: " 1100 + id + " destroyed"); 1101 return; 1102 } 1103 } 1104 1105 String filterText = null; 1106 if (value != null && value.isText()) { 1107 filterText = value.getTextValue().toString(); 1108 } 1109 1110 getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this); 1111 } 1112 1113 boolean isDestroyed() { 1114 synchronized (mLock) { 1115 return mDestroyed; 1116 } 1117 } 1118 1119 IAutoFillManagerClient getClient() { 1120 synchronized (mLock) { 1121 return mClient; 1122 } 1123 } 1124 1125 private void notifyUnavailableToClient() { 1126 synchronized (mLock) { 1127 if (!mHasCallback || mCurrentViewId == null) return; 1128 try { 1129 mClient.notifyNoFillUi(id, mCurrentViewId); 1130 } catch (RemoteException e) { 1131 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e); 1132 } 1133 } 1134 } 1135 1136 private void updateTrackedIdsLocked() { 1137 if (mResponses == null || mResponses.size() == 0) { 1138 return; 1139 } 1140 1141 // Only track the views of the last response as only those are reported back to the 1142 // service, see #showSaveLocked 1143 final FillResponse response = mResponses.valueAt(getLastResponseIndex()); 1144 1145 ArraySet<AutofillId> trackedViews = null; 1146 boolean saveOnAllViewsInvisible = false; 1147 final SaveInfo saveInfo = response.getSaveInfo(); 1148 if (saveInfo != null) { 1149 saveOnAllViewsInvisible = 1150 (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; 1151 1152 // We only need to track views if we want to save once they become invisible. 1153 if (saveOnAllViewsInvisible) { 1154 if (trackedViews == null) { 1155 trackedViews = new ArraySet<>(); 1156 } 1157 if (saveInfo.getRequiredIds() != null) { 1158 Collections.addAll(trackedViews, saveInfo.getRequiredIds()); 1159 } 1160 1161 if (saveInfo.getOptionalIds() != null) { 1162 Collections.addAll(trackedViews, saveInfo.getOptionalIds()); 1163 } 1164 } 1165 } 1166 1167 // Must also track that are part of datasets, otherwise the FillUI won't be hidden when 1168 // they go away (if they're not savable). 1169 1170 final ArrayList<Dataset> datasets = response.getDatasets(); 1171 ArraySet<AutofillId> fillableIds = null; 1172 if (datasets != null) { 1173 for (int i = 0; i < datasets.size(); i++) { 1174 final Dataset dataset = datasets.get(i); 1175 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds(); 1176 if (fieldIds == null) continue; 1177 1178 for (int j = 0; j < fieldIds.size(); j++) { 1179 final AutofillId id = fieldIds.get(j); 1180 if (trackedViews == null || !trackedViews.contains(id)) { 1181 fillableIds = ArrayUtils.add(fillableIds, id); 1182 } 1183 } 1184 } 1185 } 1186 1187 try { 1188 if (sVerbose) { 1189 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds); 1190 } 1191 mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible, 1192 toArray(fillableIds)); 1193 } catch (RemoteException e) { 1194 Slog.w(TAG, "Cannot set tracked ids", e); 1195 } 1196 } 1197 1198 private void replaceResponseLocked(@NonNull FillResponse oldResponse, 1199 @NonNull FillResponse newResponse) { 1200 // Disassociate view states with the old response 1201 setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true); 1202 // Move over the id 1203 newResponse.setRequestId(oldResponse.getRequestId()); 1204 // Replace the old response 1205 mResponses.put(newResponse.getRequestId(), newResponse); 1206 // Now process the new response 1207 processResponseLocked(newResponse); 1208 } 1209 1210 private void processResponseLocked(@NonNull FillResponse newResponse) { 1211 // Make sure we are hiding the UI which will be shown 1212 // only if handling the current response requires it. 1213 hideAllUiIfOwnedByMe(); 1214 1215 final int requestId = newResponse.getRequestId(); 1216 if (sVerbose) { 1217 Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId 1218 + ", reqId=" + requestId + ", resp=" + newResponse); 1219 } 1220 1221 if (mResponses == null) { 1222 mResponses = new SparseArray<>(4); 1223 } 1224 mResponses.put(requestId, newResponse); 1225 mClientState = newResponse.getClientState(); 1226 1227 setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false); 1228 updateTrackedIdsLocked(); 1229 1230 if (mCurrentViewId == null) { 1231 return; 1232 } 1233 1234 final ArrayList<Dataset> datasets = newResponse.getDatasets(); 1235 1236 if (datasets != null && datasets.size() == 1) { 1237 // Check if it its a single response for a manual request, in which case it should 1238 // be automatically filled 1239 final FillContext context = getFillContextByRequestIdLocked(requestId); 1240 if (context != null && (context.getStructure().getFlags() & FLAG_MANUAL_REQUEST) != 0) { 1241 Slog.d(TAG, "autofilling manual request directly"); 1242 autoFill(requestId, 0, datasets.get(0)); 1243 return; 1244 } 1245 } 1246 // Updates the UI, if necessary. 1247 final ViewState currentView = mViewStates.get(mCurrentViewId); 1248 currentView.maybeCallOnFillReady(); 1249 } 1250 1251 /** 1252 * Sets the state of all views in the given response. 1253 */ 1254 private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) { 1255 final ArrayList<Dataset> datasets = response.getDatasets(); 1256 if (datasets != null) { 1257 for (int i = 0; i < datasets.size(); i++) { 1258 final Dataset dataset = datasets.get(i); 1259 if (dataset == null) { 1260 Slog.w(TAG, "Ignoring null dataset on " + datasets); 1261 continue; 1262 } 1263 setViewStatesLocked(response, dataset, state, clearResponse); 1264 } 1265 } else if (response.getAuthentication() != null) { 1266 for (AutofillId autofillId : response.getAuthenticationIds()) { 1267 final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null); 1268 if (!clearResponse) { 1269 viewState.setResponse(response); 1270 } else { 1271 viewState.setResponse(null); 1272 } 1273 } 1274 } 1275 final SaveInfo saveInfo = response.getSaveInfo(); 1276 if (saveInfo != null) { 1277 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 1278 for (AutofillId id : requiredIds) { 1279 createOrUpdateViewStateLocked(id, state, null); 1280 } 1281 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 1282 if (optionalIds != null) { 1283 for (AutofillId id : optionalIds) { 1284 createOrUpdateViewStateLocked(id, state, null); 1285 } 1286 } 1287 } 1288 1289 final AutofillId[] authIds = response.getAuthenticationIds(); 1290 if (authIds != null) { 1291 for (AutofillId id : authIds) { 1292 createOrUpdateViewStateLocked(id, state, null); 1293 } 1294 } 1295 } 1296 1297 /** 1298 * Sets the state of all views in the given dataset and response. 1299 */ 1300 private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset, 1301 int state, boolean clearResponse) { 1302 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 1303 final ArrayList<AutofillValue> values = dataset.getFieldValues(); 1304 for (int j = 0; j < ids.size(); j++) { 1305 final AutofillId id = ids.get(j); 1306 final AutofillValue value = values.get(j); 1307 final ViewState viewState = createOrUpdateViewStateLocked(id, state, value); 1308 if (response != null) { 1309 viewState.setResponse(response); 1310 } else if (clearResponse) { 1311 viewState.setResponse(null); 1312 } 1313 } 1314 } 1315 1316 private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state, 1317 @Nullable AutofillValue value) { 1318 ViewState viewState = mViewStates.get(id); 1319 if (viewState != null) { 1320 viewState.setState(state); 1321 } else { 1322 viewState = new ViewState(this, id, null, this, state); 1323 if (sVerbose) { 1324 Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state); 1325 } 1326 mViewStates.put(id, viewState); 1327 } 1328 if ((state & ViewState.STATE_AUTOFILLED) != 0) { 1329 viewState.setAutofilledValue(value); 1330 } 1331 return viewState; 1332 } 1333 1334 /** 1335 * Resets the given state from all existing views in the given dataset. 1336 */ 1337 private void resetViewStatesLocked(@NonNull Dataset dataset, int state) { 1338 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 1339 for (int j = 0; j < ids.size(); j++) { 1340 final AutofillId id = ids.get(j); 1341 final ViewState viewState = mViewStates.get(id); 1342 if (viewState != null) { 1343 viewState.resetState(state); 1344 } 1345 } 1346 } 1347 1348 void autoFill(int requestId, int datasetIndex, Dataset dataset) { 1349 synchronized (mLock) { 1350 if (mDestroyed) { 1351 Slog.w(TAG, "Call to Session#autoFill() rejected - session: " 1352 + id + " destroyed"); 1353 return; 1354 } 1355 // Autofill it directly... 1356 if (dataset.getAuthentication() == null) { 1357 mService.setDatasetSelected(dataset.getId()); 1358 1359 autoFillApp(dataset); 1360 return; 1361 } 1362 1363 // ...or handle authentication. 1364 mService.setDatasetAuthenticationSelected(dataset.getId()); 1365 setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); 1366 final Intent fillInIntent = createAuthFillInIntent( 1367 getFillContextByRequestIdLocked(requestId).getStructure(), mClientState); 1368 1369 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, 1370 datasetIndex); 1371 startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent); 1372 } 1373 } 1374 1375 CharSequence getServiceName() { 1376 synchronized (mLock) { 1377 return mService.getServiceName(); 1378 } 1379 } 1380 1381 private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) { 1382 final Intent fillInIntent = new Intent(); 1383 fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure); 1384 fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras); 1385 return fillInIntent; 1386 } 1387 1388 private void startAuthentication(int authenticationId, IntentSender intent, 1389 Intent fillInIntent) { 1390 try { 1391 synchronized (mLock) { 1392 mClient.authenticate(id, authenticationId, intent, fillInIntent); 1393 } 1394 } catch (RemoteException e) { 1395 Slog.e(TAG, "Error launching auth intent", e); 1396 } 1397 } 1398 1399 void dumpLocked(String prefix, PrintWriter pw) { 1400 final String prefix2 = prefix + " "; 1401 pw.print(prefix); pw.print("id: "); pw.println(id); 1402 pw.print(prefix); pw.print("uid: "); pw.println(uid); 1403 pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); 1404 pw.print(prefix); pw.print("mResponses: "); 1405 if (mResponses == null) { 1406 pw.println("null"); 1407 } else { 1408 pw.println(mResponses.size()); 1409 for (int i = 0; i < mResponses.size(); i++) { 1410 pw.print(prefix2); pw.print('#'); pw.print(i); 1411 pw.print(' '); pw.println(mResponses.valueAt(i)); 1412 } 1413 } 1414 pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); 1415 pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); 1416 pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); 1417 pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving); 1418 for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { 1419 pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey()); 1420 entry.getValue().dump(prefix2, pw); 1421 } 1422 1423 pw.print(prefix); pw.print("mContexts: " ); 1424 if (mContexts != null) { 1425 int numContexts = mContexts.size(); 1426 for (int i = 0; i < numContexts; i++) { 1427 FillContext context = mContexts.get(i); 1428 1429 pw.print(prefix2); pw.print(context); 1430 if (sVerbose) { 1431 pw.println(context.getStructure() + " (look at logcat)"); 1432 1433 // TODO: add method on AssistStructure to dump on pw 1434 context.getStructure().dump(false); 1435 } 1436 } 1437 } else { 1438 pw.println("null"); 1439 } 1440 1441 pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); 1442 pw.print(prefix); pw.print("mClientState: "); pw.println( 1443 Helper.bundleToString(mClientState)); 1444 mRemoteFillService.dump(prefix, pw); 1445 } 1446 1447 void autoFillApp(Dataset dataset) { 1448 synchronized (mLock) { 1449 if (mDestroyed) { 1450 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: " 1451 + id + " destroyed"); 1452 return; 1453 } 1454 try { 1455 if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); 1456 1457 // Skip null values as a null values means no change 1458 final int entryCount = dataset.getFieldIds().size(); 1459 final List<AutofillId> ids = new ArrayList<>(entryCount); 1460 final List<AutofillValue> values = new ArrayList<>(entryCount); 1461 boolean waitingDatasetAuth = false; 1462 for (int i = 0; i < entryCount; i++) { 1463 if (dataset.getFieldValues().get(i) == null) { 1464 continue; 1465 } 1466 final AutofillId viewId = dataset.getFieldIds().get(i); 1467 ids.add(viewId); 1468 values.add(dataset.getFieldValues().get(i)); 1469 final ViewState viewState = mViewStates.get(viewId); 1470 if (viewState != null 1471 && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) { 1472 if (sVerbose) { 1473 Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth"); 1474 } 1475 waitingDatasetAuth = true; 1476 viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH); 1477 } 1478 } 1479 if (!ids.isEmpty()) { 1480 if (waitingDatasetAuth) { 1481 hideFillUiIfOwnedByMe(); 1482 } 1483 mClient.autofill(id, ids, values); 1484 setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false); 1485 } 1486 } catch (RemoteException e) { 1487 Slog.w(TAG, "Error autofilling activity: " + e); 1488 } 1489 } 1490 } 1491 1492 private AutoFillUI getUiForShowing() { 1493 synchronized (mLock) { 1494 mUi.setCallback(this); 1495 return mUi; 1496 } 1497 } 1498 1499 void destroyLocked() { 1500 if (mDestroyed) { 1501 return; 1502 } 1503 mRemoteFillService.destroy(); 1504 hideAllUiIfOwnedByMe(); 1505 mUi.clearCallback(this); 1506 mDestroyed = true; 1507 mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName); 1508 } 1509 1510 private void hideAllUiIfOwnedByMe() { 1511 mUi.hideAll(this); 1512 } 1513 1514 private void hideFillUiIfOwnedByMe() { 1515 mUi.hideFillUi(this); 1516 } 1517 1518 private void removeSelf() { 1519 synchronized (mLock) { 1520 removeSelfLocked(); 1521 } 1522 } 1523 1524 void removeSelfLocked() { 1525 if (sVerbose) Slog.v(TAG, "removeSelfLocked()"); 1526 if (mDestroyed) { 1527 Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: " 1528 + id + " destroyed"); 1529 return; 1530 } 1531 destroyLocked(); 1532 mService.removeSessionLocked(id); 1533 } 1534 1535 private int getLastResponseIndex() { 1536 // The response ids are monotonically increasing so 1537 // we just find the largest id which is the last. We 1538 // do not rely on the internal ordering in sparse 1539 // array to avoid - wow this stopped working!? 1540 int lastResponseIdx = -1; 1541 int lastResponseId = -1; 1542 final int responseCount = mResponses.size(); 1543 for (int i = 0; i < responseCount; i++) { 1544 if (mResponses.keyAt(i) > lastResponseId) { 1545 lastResponseIdx = i; 1546 } 1547 } 1548 return lastResponseIdx; 1549 } 1550} 1551