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