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