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