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