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 17 package com.android.server.autofill; 18 19 import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS; 20 import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE; 21 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; 22 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 23 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; 24 import static android.view.autofill.AutofillManager.ACTION_START_SESSION; 25 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; 26 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; 27 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; 28 29 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 30 import static com.android.server.autofill.Helper.getNumericValue; 31 import static com.android.server.autofill.Helper.sDebug; 32 import static com.android.server.autofill.Helper.sPartitionMaxCount; 33 import static com.android.server.autofill.Helper.sVerbose; 34 import static com.android.server.autofill.Helper.toArray; 35 import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION; 36 37 import android.annotation.NonNull; 38 import android.annotation.Nullable; 39 import android.app.Activity; 40 import android.app.ActivityManager; 41 import android.app.IAssistDataReceiver; 42 import android.app.assist.AssistStructure; 43 import android.app.assist.AssistStructure.AutofillOverlay; 44 import android.app.assist.AssistStructure.ViewNode; 45 import android.content.ComponentName; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.content.IntentSender; 49 import android.graphics.Bitmap; 50 import android.graphics.Rect; 51 import android.metrics.LogMaker; 52 import android.os.Binder; 53 import android.os.Bundle; 54 import android.os.Handler; 55 import android.os.IBinder; 56 import android.os.IBinder.DeathRecipient; 57 import android.os.Parcelable; 58 import android.os.RemoteCallback; 59 import android.os.RemoteException; 60 import android.os.SystemClock; 61 import android.service.autofill.AutofillFieldClassificationService.Scores; 62 import android.service.autofill.AutofillService; 63 import android.service.autofill.Dataset; 64 import android.service.autofill.FieldClassification; 65 import android.service.autofill.FieldClassification.Match; 66 import android.service.autofill.FillContext; 67 import android.service.autofill.FillRequest; 68 import android.service.autofill.FillResponse; 69 import android.service.autofill.InternalSanitizer; 70 import android.service.autofill.InternalValidator; 71 import android.service.autofill.SaveInfo; 72 import android.service.autofill.SaveRequest; 73 import android.service.autofill.UserData; 74 import android.service.autofill.ValueFinder; 75 import android.util.ArrayMap; 76 import android.util.ArraySet; 77 import android.util.LocalLog; 78 import android.util.Slog; 79 import android.util.SparseArray; 80 import android.util.TimeUtils; 81 import android.view.KeyEvent; 82 import android.view.autofill.AutofillId; 83 import android.view.autofill.AutofillManager; 84 import android.view.autofill.AutofillValue; 85 import android.view.autofill.IAutoFillManagerClient; 86 import android.view.autofill.IAutofillWindowPresenter; 87 88 import com.android.internal.R; 89 import com.android.internal.annotations.GuardedBy; 90 import com.android.internal.logging.MetricsLogger; 91 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 92 import com.android.internal.util.ArrayUtils; 93 import com.android.server.autofill.ui.AutoFillUI; 94 import com.android.server.autofill.ui.PendingUi; 95 96 import java.io.PrintWriter; 97 import java.io.StringWriter; 98 import java.util.ArrayList; 99 import java.util.Arrays; 100 import java.util.Collection; 101 import java.util.Collections; 102 import java.util.List; 103 import java.util.Objects; 104 import java.util.concurrent.atomic.AtomicInteger; 105 106 /** 107 * A session for a given activity. 108 * 109 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track 110 * of the current {@link ViewState} to display the appropriate UI. 111 * 112 * <p>Although the autofill requests and callbacks are stateless from the service's point of 113 * view, we need to keep state in the framework side for cases such as authentication. For 114 * example, when service return a {@link FillResponse} that contains all the fields needed 115 * to fill the activity but it requires authentication first, that response need to be held 116 * until the user authenticates or it times out. 117 */ 118 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, 119 AutoFillUI.AutoFillUiCallback, ValueFinder { 120 private static final String TAG = "AutofillSession"; 121 122 private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; 123 124 private final AutofillManagerServiceImpl mService; 125 private final Handler mHandler; 126 private final Object mLock; 127 private final AutoFillUI mUi; 128 129 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 130 131 private static AtomicInteger sIdCounter = new AtomicInteger(); 132 133 /** Id of the session */ 134 public final int id; 135 136 /** uid the session is for */ 137 public final int uid; 138 139 /** Flags used to start the session */ 140 public final int mFlags; 141 142 @GuardedBy("mLock") 143 @NonNull private IBinder mActivityToken; 144 145 /** Component that's being auto-filled */ 146 @NonNull private final ComponentName mComponentName; 147 148 /** Whether the app being autofilled is running in compat mode. */ 149 private final boolean mCompatMode; 150 151 /** Node representing the URL bar on compat mode. */ 152 @GuardedBy("mLock") 153 private ViewNode mUrlBar; 154 155 @GuardedBy("mLock") 156 private boolean mSaveOnAllViewsInvisible; 157 158 @GuardedBy("mLock") 159 private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); 160 161 /** 162 * Id of the View currently being displayed. 163 */ 164 @GuardedBy("mLock") 165 @Nullable private AutofillId mCurrentViewId; 166 167 @GuardedBy("mLock") 168 private IAutoFillManagerClient mClient; 169 170 @GuardedBy("mLock") 171 private DeathRecipient mClientVulture; 172 173 private final RemoteFillService mRemoteFillService; 174 175 @GuardedBy("mLock") 176 private SparseArray<FillResponse> mResponses; 177 178 /** 179 * Contexts read from the app; they will be updated (sanitized, change values for save) before 180 * sent to {@link AutofillService}. Ordered by the time they were read. 181 */ 182 @GuardedBy("mLock") 183 private ArrayList<FillContext> mContexts; 184 185 /** 186 * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. 187 */ 188 private boolean mHasCallback; 189 190 /** 191 * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved 192 * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls. 193 */ 194 @GuardedBy("mLock") 195 private Bundle mClientState; 196 197 @GuardedBy("mLock") 198 private boolean mDestroyed; 199 200 /** Whether the session is currently saving. */ 201 @GuardedBy("mLock") 202 private boolean mIsSaving; 203 204 /** 205 * Helper used to handle state of Save UI when it must be hiding to show a custom description 206 * link and later recovered. 207 */ 208 @GuardedBy("mLock") 209 private PendingUi mPendingSaveUi; 210 211 /** 212 * List of dataset ids selected by the user. 213 */ 214 @GuardedBy("mLock") 215 private ArrayList<String> mSelectedDatasetIds; 216 217 /** 218 * When the session started (using elapsed time since boot). 219 */ 220 private final long mStartTime; 221 222 /** 223 * When the UI was shown for the first time (using elapsed time since boot). 224 */ 225 @GuardedBy("mLock") 226 private long mUiShownTime; 227 228 @GuardedBy("mLock") 229 private final LocalLog mUiLatencyHistory; 230 231 @GuardedBy("mLock") 232 private final LocalLog mWtfHistory; 233 234 /** 235 * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. 236 */ 237 @GuardedBy("mLock") 238 private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1); 239 240 /** 241 * Receiver of assist data from the app's {@link Activity}. 242 */ 243 private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() { 244 @Override 245 public void onHandleAssistData(Bundle resultData) throws RemoteException { 246 final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE); 247 if (structure == null) { 248 Slog.e(TAG, "No assist structure - app might have crashed providing it"); 249 return; 250 } 251 252 final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS); 253 if (receiverExtras == null) { 254 Slog.e(TAG, "No receiver extras - app might have crashed providing it"); 255 return; 256 } 257 258 final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); 259 260 if (sVerbose) { 261 Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure); 262 } 263 264 final FillRequest request; 265 synchronized (mLock) { 266 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(), 267 // even if if the activity is gone by then, but structure .ensureData() gives a 268 // ONE_WAY warning because system_service could block on app calls. We need to 269 // change AssistStructure so it provides a "one-way" writeToParcel() method that 270 // sends all the data 271 try { 272 structure.ensureDataForAutofill(); 273 } catch (RuntimeException e) { 274 wtf(e, "Exception lazy loading assist structure for %s: %s", 275 structure.getActivityComponent(), e); 276 return; 277 } 278 279 // Sanitize structure before it's sent to service. 280 final ComponentName componentNameFromApp = structure.getActivityComponent(); 281 if (componentNameFromApp == null || !mComponentName.getPackageName() 282 .equals(componentNameFromApp.getPackageName())) { 283 Slog.w(TAG, "Activity " + mComponentName + " forged different component on " 284 + "AssistStructure: " + componentNameFromApp); 285 structure.setActivityComponent(mComponentName); 286 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT) 287 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME, 288 componentNameFromApp == null ? "null" 289 : componentNameFromApp.flattenToShortString())); 290 } 291 if (mCompatMode) { 292 // Sanitize URL bar, if needed 293 final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode( 294 mComponentName.getPackageName()); 295 if (sDebug) { 296 Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds)); 297 } 298 if (urlBarIds != null) { 299 mUrlBar = Helper.sanitizeUrlBar(structure, urlBarIds); 300 if (mUrlBar != null) { 301 final AutofillId urlBarId = mUrlBar.getAutofillId(); 302 if (sDebug) { 303 Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain " 304 + mUrlBar.getWebDomain()); 305 } 306 final ViewState viewState = new ViewState(Session.this, urlBarId, 307 Session.this, ViewState.STATE_URL_BAR); 308 mViewStates.put(urlBarId, viewState); 309 } 310 } 311 } 312 structure.sanitizeForParceling(true); 313 314 // Flags used to start the session. 315 final int flags = structure.getFlags(); 316 317 if (mContexts == null) { 318 mContexts = new ArrayList<>(1); 319 } 320 mContexts.add(new FillContext(requestId, structure)); 321 322 cancelCurrentRequestLocked(); 323 324 final int numContexts = mContexts.size(); 325 for (int i = 0; i < numContexts; i++) { 326 fillContextWithAllowedValuesLocked(mContexts.get(i), flags); 327 } 328 329 // Dispatch a snapshot of the current contexts list since it may change 330 // until the dispatch happens. The items in the list don't need to be cloned 331 // since we don't hold on them anywhere else. The client state is not touched 332 // by us, so no need to copy. 333 request = new FillRequest(requestId, new ArrayList<>(mContexts), 334 mClientState, flags); 335 } 336 337 mRemoteFillService.onFillRequest(request); 338 } 339 340 @Override 341 public void onHandleAssistScreenshot(Bitmap screenshot) { 342 // Do nothing 343 } 344 }; 345 346 /** 347 * Returns the ids of all entries in {@link #mViewStates} in the same order. 348 */ 349 @GuardedBy("mLock") 350 private AutofillId[] getIdsOfAllViewStatesLocked() { 351 final int numViewState = mViewStates.size(); 352 final AutofillId[] ids = new AutofillId[numViewState]; 353 for (int i = 0; i < numViewState; i++) { 354 ids[i] = mViewStates.valueAt(i).id; 355 } 356 357 return ids; 358 } 359 360 @Override 361 @Nullable 362 public String findByAutofillId(@NonNull AutofillId id) { 363 synchronized (mLock) { 364 AutofillValue value = findValueLocked(id); 365 if (value != null) { 366 if (value.isText()) { 367 return value.getTextValue().toString(); 368 } 369 370 if (value.isList()) { 371 final CharSequence[] options = getAutofillOptionsFromContextsLocked(id); 372 if (options != null) { 373 final int index = value.getListValue(); 374 final CharSequence option = options[index]; 375 return option != null ? option.toString() : null; 376 } else { 377 Slog.w(TAG, "findByAutofillId(): no autofill options for id " + id); 378 } 379 } 380 } 381 } 382 return null; 383 } 384 385 @Override 386 public AutofillValue findRawValueByAutofillId(AutofillId id) { 387 synchronized (mLock) { 388 return findValueLocked(id); 389 } 390 } 391 392 /** 393 * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, 394 * or {@code null} when not found on either of them. 395 */ 396 @GuardedBy("mLock") 397 private AutofillValue findValueLocked(@NonNull AutofillId id) { 398 final ViewState state = mViewStates.get(id); 399 if (state == null) { 400 if (sDebug) Slog.d(TAG, "findValueLocked(): no view state for " + id); 401 return null; 402 } 403 AutofillValue value = state.getCurrentValue(); 404 if (value == null) { 405 if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + id); 406 value = getValueFromContextsLocked(id); 407 } 408 return value; 409 } 410 411 /** 412 * Updates values of the nodes in the context's structure so that: 413 * 414 * - proper node is focused 415 * - autofillValue is sent back to service when it was previously autofilled 416 * - autofillValue is sent in the view used to force a request 417 * 418 * @param fillContext The context to be filled 419 * @param flags The flags that started the session 420 */ 421 @GuardedBy("mLock") 422 private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) { 423 final ViewNode[] nodes = fillContext 424 .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); 425 426 final int numViewState = mViewStates.size(); 427 for (int i = 0; i < numViewState; i++) { 428 final ViewState viewState = mViewStates.valueAt(i); 429 430 final ViewNode node = nodes[i]; 431 if (node == null) { 432 if (sVerbose) { 433 Slog.v(TAG, 434 "fillContextWithAllowedValuesLocked(): no node for " + viewState.id); 435 } 436 continue; 437 } 438 439 final AutofillValue currentValue = viewState.getCurrentValue(); 440 final AutofillValue filledValue = viewState.getAutofilledValue(); 441 final AutofillOverlay overlay = new AutofillOverlay(); 442 443 // Sanitizes the value if the current value matches what the service sent. 444 if (filledValue != null && filledValue.equals(currentValue)) { 445 overlay.value = currentValue; 446 } 447 448 if (mCurrentViewId != null) { 449 // Updates the focus value. 450 overlay.focused = mCurrentViewId.equals(viewState.id); 451 // Sanitizes the value of the focused field in a manual request. 452 if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) { 453 overlay.value = currentValue; 454 } 455 } 456 node.setAutofillOverlay(overlay); 457 } 458 } 459 460 /** 461 * Cancels the last request sent to the {@link #mRemoteFillService}. 462 */ 463 @GuardedBy("mLock") 464 private void cancelCurrentRequestLocked() { 465 final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); 466 467 // Remove the FillContext as there will never be a response for the service 468 if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { 469 final int numContexts = mContexts.size(); 470 471 // It is most likely the last context, hence search backwards 472 for (int i = numContexts - 1; i >= 0; i--) { 473 if (mContexts.get(i).getRequestId() == canceledRequest) { 474 if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); 475 mContexts.remove(i); 476 break; 477 } 478 } 479 } 480 } 481 482 /** 483 * Reads a new structure and then request a new fill response from the fill service. 484 */ 485 @GuardedBy("mLock") 486 private void requestNewFillResponseLocked(int flags) { 487 int requestId; 488 489 do { 490 requestId = sIdCounter.getAndIncrement(); 491 } while (requestId == INVALID_REQUEST_ID); 492 493 // Create a metrics log for the request 494 final int ordinal = mRequestLogs.size() + 1; 495 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST) 496 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal); 497 if (flags != 0) { 498 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags); 499 } 500 mRequestLogs.put(requestId, log); 501 502 if (sVerbose) { 503 Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" 504 + requestId + ", flags=" + flags); 505 } 506 507 // If the focus changes very quickly before the first request is returned each focus change 508 // triggers a new partition and we end up with many duplicate partitions. This is 509 // enhanced as the focus change can be much faster than the taking of the assist structure. 510 // Hence remove the currently queued request and replace it with the one queued after the 511 // structure is taken. This causes only one fill request per bust of focus changes. 512 cancelCurrentRequestLocked(); 513 514 try { 515 final Bundle receiverExtras = new Bundle(); 516 receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); 517 final long identity = Binder.clearCallingIdentity(); 518 try { 519 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver, 520 receiverExtras, mActivityToken, flags)) { 521 Slog.w(TAG, "failed to request autofill data for " + mActivityToken); 522 } 523 } finally { 524 Binder.restoreCallingIdentity(identity); 525 } 526 } catch (RemoteException e) { 527 // Should not happen, it's a local call. 528 } 529 } 530 531 Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, 532 @NonNull Context context, @NonNull Handler handler, int userId, 533 @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, 534 @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, 535 @NonNull LocalLog wtfHistory, 536 @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName, 537 boolean compatMode, boolean bindInstantServiceAllowed, int flags) { 538 id = sessionId; 539 mFlags = flags; 540 this.uid = uid; 541 mStartTime = SystemClock.elapsedRealtime(); 542 mService = service; 543 mLock = lock; 544 mUi = ui; 545 mHandler = handler; 546 mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this, 547 bindInstantServiceAllowed); 548 mActivityToken = activityToken; 549 mHasCallback = hasCallback; 550 mUiLatencyHistory = uiLatencyHistory; 551 mWtfHistory = wtfHistory; 552 mComponentName = componentName; 553 mCompatMode = compatMode; 554 setClientLocked(client); 555 556 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) 557 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); 558 } 559 560 /** 561 * Gets the currently registered activity token 562 * 563 * @return The activity token 564 */ 565 @GuardedBy("mLock") 566 @NonNull IBinder getActivityTokenLocked() { 567 return mActivityToken; 568 } 569 570 /** 571 * Sets new activity and client for this session. 572 * 573 * @param newActivity The token of the new activity 574 * @param newClient The client receiving autofill callbacks 575 */ 576 void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { 577 synchronized (mLock) { 578 if (mDestroyed) { 579 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: " 580 + id + " destroyed"); 581 return; 582 } 583 mActivityToken = newActivity; 584 setClientLocked(newClient); 585 586 // The tracked id are not persisted in the client, hence update them 587 updateTrackedIdsLocked(); 588 } 589 } 590 591 @GuardedBy("mLock") 592 private void setClientLocked(@NonNull IBinder client) { 593 unlinkClientVultureLocked(); 594 mClient = IAutoFillManagerClient.Stub.asInterface(client); 595 mClientVulture = () -> { 596 Slog.d(TAG, "handling death of " + mActivityToken + " when saving=" + mIsSaving); 597 synchronized (mLock) { 598 if (mIsSaving) { 599 mUi.hideFillUi(this); 600 } else { 601 mUi.destroyAll(mPendingSaveUi, this, false); 602 } 603 } 604 }; 605 try { 606 mClient.asBinder().linkToDeath(mClientVulture, 0); 607 } catch (RemoteException e) { 608 Slog.w(TAG, "could not set binder death listener on autofill client: " + e); 609 } 610 } 611 612 @GuardedBy("mLock") 613 private void unlinkClientVultureLocked() { 614 if (mClient != null && mClientVulture != null) { 615 final boolean unlinked = mClient.asBinder().unlinkToDeath(mClientVulture, 0); 616 if (!unlinked) { 617 Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken); 618 } 619 } 620 } 621 622 // FillServiceCallbacks 623 @Override 624 public void onFillRequestSuccess(int requestId, @Nullable FillResponse response, 625 @NonNull String servicePackageName, int requestFlags) { 626 final AutofillId[] fieldClassificationIds; 627 628 final LogMaker requestLog; 629 630 synchronized (mLock) { 631 if (mDestroyed) { 632 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " 633 + id + " destroyed"); 634 return; 635 } 636 637 requestLog = mRequestLogs.get(requestId); 638 if (requestLog != null) { 639 requestLog.setType(MetricsEvent.TYPE_SUCCESS); 640 } else { 641 Slog.w(TAG, "onFillRequestSuccess(): no request log for id " + requestId); 642 } 643 if (response == null) { 644 if (requestLog != null) { 645 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1); 646 } 647 processNullResponseLocked(requestFlags); 648 return; 649 } 650 651 fieldClassificationIds = response.getFieldClassificationIds(); 652 if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) { 653 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled"); 654 processNullResponseLocked(requestFlags); 655 return; 656 } 657 } 658 659 mService.setLastResponse(id, response); 660 661 int sessionFinishedState = 0; 662 final long disableDuration = response.getDisableDuration(); 663 if (disableDuration > 0) { 664 final int flags = response.getFlags(); 665 if (sDebug) { 666 final StringBuilder message = new StringBuilder("Service disabled autofill for ") 667 .append(mComponentName) 668 .append(": flags=").append(flags) 669 .append(", duration="); 670 TimeUtils.formatDuration(disableDuration, message); 671 Slog.d(TAG, message.toString()); 672 } 673 if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) { 674 mService.disableAutofillForActivity(mComponentName, disableDuration, 675 id, mCompatMode); 676 } else { 677 mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, 678 id, mCompatMode); 679 } 680 sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE; 681 } 682 683 if (((response.getDatasets() == null || response.getDatasets().isEmpty()) 684 && response.getAuthentication() == null) 685 || disableDuration > 0) { 686 // Response is "empty" from an UI point of view, need to notify client. 687 notifyUnavailableToClient(sessionFinishedState); 688 } 689 690 if (requestLog != null) { 691 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, 692 response.getDatasets() == null ? 0 : response.getDatasets().size()); 693 if (fieldClassificationIds != null) { 694 requestLog.addTaggedData( 695 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS, 696 fieldClassificationIds.length); 697 } 698 } 699 700 synchronized (mLock) { 701 processResponseLocked(response, null, requestFlags); 702 } 703 } 704 705 // FillServiceCallbacks 706 @Override 707 public void onFillRequestFailure(int requestId, @Nullable CharSequence message, 708 @NonNull String servicePackageName) { 709 onFillRequestFailureOrTimeout(requestId, false, message, servicePackageName); 710 } 711 712 // FillServiceCallbacks 713 @Override 714 public void onFillRequestTimeout(int requestId, @NonNull String servicePackageName) { 715 onFillRequestFailureOrTimeout(requestId, true, null, servicePackageName); 716 } 717 718 private void onFillRequestFailureOrTimeout(int requestId, boolean timedOut, 719 @Nullable CharSequence message, @NonNull String servicePackageName) { 720 synchronized (mLock) { 721 if (mDestroyed) { 722 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId 723 + ") rejected - session: " + id + " destroyed"); 724 return; 725 } 726 mService.resetLastResponse(); 727 final LogMaker requestLog = mRequestLogs.get(requestId); 728 if (requestLog == null) { 729 Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId); 730 } else { 731 requestLog.setType(timedOut ? MetricsEvent.TYPE_CLOSE : MetricsEvent.TYPE_FAILURE); 732 } 733 } 734 if (message != null) { 735 getUiForShowing().showError(message, this); 736 } 737 removeSelf(); 738 } 739 740 // FillServiceCallbacks 741 @Override 742 public void onSaveRequestSuccess(@NonNull String servicePackageName, 743 @Nullable IntentSender intentSender) { 744 synchronized (mLock) { 745 mIsSaving = false; 746 747 if (mDestroyed) { 748 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " 749 + id + " destroyed"); 750 return; 751 } 752 } 753 LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) 754 .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN); 755 mMetricsLogger.write(log); 756 if (intentSender != null) { 757 if (sDebug) Slog.d(TAG, "Starting intent sender on save()"); 758 startIntentSender(intentSender); 759 } 760 761 // Nothing left to do... 762 removeSelf(); 763 } 764 765 // FillServiceCallbacks 766 @Override 767 public void onSaveRequestFailure(@Nullable CharSequence message, 768 @NonNull String servicePackageName) { 769 synchronized (mLock) { 770 mIsSaving = false; 771 772 if (mDestroyed) { 773 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " 774 + id + " destroyed"); 775 return; 776 } 777 } 778 LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) 779 .setType(MetricsEvent.TYPE_FAILURE); 780 mMetricsLogger.write(log); 781 782 getUiForShowing().showError(message, this); 783 removeSelf(); 784 } 785 786 /** 787 * Gets the {@link FillContext} for a request. 788 * 789 * @param requestId The id of the request 790 * 791 * @return The context or {@code null} if there is no context 792 */ 793 @GuardedBy("mLock") 794 @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) { 795 if (mContexts == null) { 796 return null; 797 } 798 799 int numContexts = mContexts.size(); 800 for (int i = 0; i < numContexts; i++) { 801 FillContext context = mContexts.get(i); 802 803 if (context.getRequestId() == requestId) { 804 return context; 805 } 806 } 807 808 return null; 809 } 810 811 // FillServiceCallbacks 812 @Override 813 public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) { 814 if (sDebug) { 815 Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex 816 + "; intentSender=" + intent); 817 } 818 final Intent fillInIntent; 819 synchronized (mLock) { 820 if (mDestroyed) { 821 Slog.w(TAG, "Call to Session#authenticate() rejected - session: " 822 + id + " destroyed"); 823 return; 824 } 825 fillInIntent = createAuthFillInIntentLocked(requestId, extras); 826 if (fillInIntent == null) { 827 forceRemoveSelfLocked(); 828 return; 829 } 830 } 831 832 mService.setAuthenticationSelected(id, mClientState); 833 834 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); 835 mHandler.sendMessage(obtainMessage( 836 Session::startAuthentication, 837 this, authenticationId, intent, fillInIntent)); 838 } 839 840 // FillServiceCallbacks 841 @Override 842 public void onServiceDied(RemoteFillService service) { 843 // TODO(b/337565347): implement 844 } 845 846 // AutoFillUiCallback 847 @Override 848 public void fill(int requestId, int datasetIndex, Dataset dataset) { 849 synchronized (mLock) { 850 if (mDestroyed) { 851 Slog.w(TAG, "Call to Session#fill() rejected - session: " 852 + id + " destroyed"); 853 return; 854 } 855 } 856 mHandler.sendMessage(obtainMessage( 857 Session::autoFill, 858 this, requestId, datasetIndex, dataset, true)); 859 } 860 861 // AutoFillUiCallback 862 @Override 863 public void save() { 864 synchronized (mLock) { 865 if (mDestroyed) { 866 Slog.w(TAG, "Call to Session#save() rejected - session: " 867 + id + " destroyed"); 868 return; 869 } 870 } 871 mHandler.sendMessage(obtainMessage( 872 AutofillManagerServiceImpl::handleSessionSave, 873 mService, this)); 874 } 875 876 // AutoFillUiCallback 877 @Override 878 public void cancelSave() { 879 synchronized (mLock) { 880 mIsSaving = false; 881 882 if (mDestroyed) { 883 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " 884 + id + " destroyed"); 885 return; 886 } 887 } 888 mHandler.sendMessage(obtainMessage( 889 Session::removeSelf, this)); 890 } 891 892 // AutoFillUiCallback 893 @Override 894 public void requestShowFillUi(AutofillId id, int width, int height, 895 IAutofillWindowPresenter presenter) { 896 synchronized (mLock) { 897 if (mDestroyed) { 898 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: " 899 + id + " destroyed"); 900 return; 901 } 902 if (id.equals(mCurrentViewId)) { 903 try { 904 final ViewState view = mViewStates.get(id); 905 mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(), 906 presenter); 907 } catch (RemoteException e) { 908 Slog.e(TAG, "Error requesting to show fill UI", e); 909 } 910 } else { 911 if (sDebug) { 912 Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" 913 + mCurrentViewId + ") anymore"); 914 } 915 } 916 } 917 } 918 919 @Override 920 public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) { 921 synchronized (mLock) { 922 if (mDestroyed) { 923 Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: " 924 + id + " destroyed"); 925 return; 926 } 927 if (id.equals(mCurrentViewId)) { 928 try { 929 mClient.dispatchUnhandledKey(this.id, id, keyEvent); 930 } catch (RemoteException e) { 931 Slog.e(TAG, "Error requesting to dispatch unhandled key", e); 932 } 933 } else { 934 Slog.w(TAG, "Do not dispatch unhandled key on " + id 935 + " as it is not the current view (" + mCurrentViewId + ") anymore"); 936 } 937 } 938 } 939 940 // AutoFillUiCallback 941 @Override 942 public void requestHideFillUi(AutofillId id) { 943 synchronized (mLock) { 944 // NOTE: We allow this call in a destroyed state as the UI is 945 // asked to go away after we get destroyed, so let it do that. 946 try { 947 mClient.requestHideFillUi(this.id, id); 948 } catch (RemoteException e) { 949 Slog.e(TAG, "Error requesting to hide fill UI", e); 950 } 951 } 952 } 953 954 // AutoFillUiCallback 955 @Override 956 public void startIntentSender(IntentSender intentSender) { 957 synchronized (mLock) { 958 if (mDestroyed) { 959 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: " 960 + id + " destroyed"); 961 return; 962 } 963 removeSelfLocked(); 964 } 965 mHandler.sendMessage(obtainMessage( 966 Session::doStartIntentSender, 967 this, intentSender)); 968 } 969 970 private void doStartIntentSender(IntentSender intentSender) { 971 try { 972 synchronized (mLock) { 973 mClient.startIntentSender(intentSender, null); 974 } 975 } catch (RemoteException e) { 976 Slog.e(TAG, "Error launching auth intent", e); 977 } 978 } 979 980 @GuardedBy("mLock") 981 void setAuthenticationResultLocked(Bundle data, int authenticationId) { 982 if (mDestroyed) { 983 Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " 984 + id + " destroyed"); 985 return; 986 } 987 if (mResponses == null) { 988 // Typically happens when app explicitly called cancel() while the service was showing 989 // the auth UI. 990 Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses"); 991 removeSelf(); 992 return; 993 } 994 final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); 995 final FillResponse authenticatedResponse = mResponses.get(requestId); 996 if (authenticatedResponse == null || data == null) { 997 removeSelf(); 998 return; 999 } 1000 1001 final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId( 1002 authenticationId); 1003 // Authenticated a dataset - reset view state regardless if we got a response or a dataset 1004 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { 1005 final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx); 1006 if (dataset == null) { 1007 removeSelf(); 1008 return; 1009 } 1010 } 1011 1012 final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); 1013 final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); 1014 if (sDebug) { 1015 Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result 1016 + ", clientState=" + newClientState); 1017 } 1018 if (result instanceof FillResponse) { 1019 logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED); 1020 replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState); 1021 } else if (result instanceof Dataset) { 1022 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { 1023 logAuthenticationStatusLocked(requestId, 1024 MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); 1025 if (newClientState != null) { 1026 if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); 1027 mClientState = newClientState; 1028 } 1029 final Dataset dataset = (Dataset) result; 1030 authenticatedResponse.getDatasets().set(datasetIdx, dataset); 1031 autoFill(requestId, datasetIdx, dataset, false); 1032 } else { 1033 logAuthenticationStatusLocked(requestId, 1034 MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION); 1035 } 1036 } else { 1037 if (result != null) { 1038 Slog.w(TAG, "service returned invalid auth type: " + result); 1039 } 1040 logAuthenticationStatusLocked(requestId, 1041 MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION); 1042 processNullResponseLocked(0); 1043 } 1044 } 1045 1046 @GuardedBy("mLock") 1047 void setHasCallbackLocked(boolean hasIt) { 1048 if (mDestroyed) { 1049 Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " 1050 + id + " destroyed"); 1051 return; 1052 } 1053 mHasCallback = hasIt; 1054 } 1055 1056 @GuardedBy("mLock") 1057 @Nullable 1058 private FillResponse getLastResponseLocked(@Nullable String logPrefix) { 1059 if (mContexts == null) { 1060 if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts"); 1061 return null; 1062 } 1063 if (mResponses == null) { 1064 // Happens when the activity / session was finished before the service replied, or 1065 // when the service cannot autofill it (and returned a null response). 1066 if (sVerbose && logPrefix != null) { 1067 Slog.v(TAG, logPrefix + ": no responses on session"); 1068 } 1069 return null; 1070 } 1071 1072 final int lastResponseIdx = getLastResponseIndexLocked(); 1073 if (lastResponseIdx < 0) { 1074 if (logPrefix != null) { 1075 Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses 1076 + ", mViewStates=" + mViewStates); 1077 } 1078 return null; 1079 } 1080 1081 final FillResponse response = mResponses.valueAt(lastResponseIdx); 1082 if (sVerbose && logPrefix != null) { 1083 Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts 1084 + ", mViewStates=" + mViewStates); 1085 } 1086 return response; 1087 } 1088 1089 @GuardedBy("mLock") 1090 @Nullable 1091 private SaveInfo getSaveInfoLocked() { 1092 final FillResponse response = getLastResponseLocked(null); 1093 return response == null ? null : response.getSaveInfo(); 1094 } 1095 1096 /** 1097 * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} 1098 * when necessary. 1099 */ 1100 public void logContextCommitted() { 1101 mHandler.sendMessage(obtainMessage( 1102 Session::doLogContextCommitted, this)); 1103 } 1104 1105 private void doLogContextCommitted() { 1106 synchronized (mLock) { 1107 logContextCommittedLocked(); 1108 } 1109 } 1110 1111 @GuardedBy("mLock") 1112 private void logContextCommittedLocked() { 1113 final FillResponse lastResponse = getLastResponseLocked("logContextCommited()"); 1114 if (lastResponse == null) return; 1115 1116 final int flags = lastResponse.getFlags(); 1117 if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) { 1118 if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags); 1119 return; 1120 } 1121 1122 ArraySet<String> ignoredDatasets = null; 1123 ArrayList<AutofillId> changedFieldIds = null; 1124 ArrayList<String> changedDatasetIds = null; 1125 ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null; 1126 1127 boolean hasAtLeastOneDataset = false; 1128 final int responseCount = mResponses.size(); 1129 for (int i = 0; i < responseCount; i++) { 1130 final FillResponse response = mResponses.valueAt(i); 1131 final List<Dataset> datasets = response.getDatasets(); 1132 if (datasets == null || datasets.isEmpty()) { 1133 if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i); 1134 } else { 1135 for (int j = 0; j < datasets.size(); j++) { 1136 final Dataset dataset = datasets.get(j); 1137 final String datasetId = dataset.getId(); 1138 if (datasetId == null) { 1139 if (sVerbose) { 1140 Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset); 1141 } 1142 } else { 1143 hasAtLeastOneDataset = true; 1144 if (mSelectedDatasetIds == null 1145 || !mSelectedDatasetIds.contains(datasetId)) { 1146 if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId); 1147 if (ignoredDatasets == null) { 1148 ignoredDatasets = new ArraySet<>(); 1149 } 1150 ignoredDatasets.add(datasetId); 1151 } 1152 } 1153 } 1154 } 1155 } 1156 final AutofillId[] fieldClassificationIds = lastResponse.getFieldClassificationIds(); 1157 1158 if (!hasAtLeastOneDataset && fieldClassificationIds == null) { 1159 if (sVerbose) { 1160 Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields " 1161 + "classification ids)"); 1162 } 1163 return; 1164 } 1165 1166 final UserData userData = mService.getUserData(); 1167 1168 for (int i = 0; i < mViewStates.size(); i++) { 1169 final ViewState viewState = mViewStates.valueAt(i); 1170 final int state = viewState.getState(); 1171 1172 // When value changed, we need to log if it was: 1173 // - autofilled -> changedDatasetIds 1174 // - not autofilled but matches a dataset value -> manuallyFilledIds 1175 if ((state & ViewState.STATE_CHANGED) != 0) { 1176 // Check if autofilled value was changed 1177 if ((state & ViewState.STATE_AUTOFILLED) != 0) { 1178 final String datasetId = viewState.getDatasetId(); 1179 if (datasetId == null) { 1180 // Sanity check - should never happen. 1181 Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState); 1182 continue; 1183 } 1184 1185 // Must first check if final changed value is not the same as value sent by 1186 // service. 1187 final AutofillValue autofilledValue = viewState.getAutofilledValue(); 1188 final AutofillValue currentValue = viewState.getCurrentValue(); 1189 if (autofilledValue != null && autofilledValue.equals(currentValue)) { 1190 if (sDebug) { 1191 Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState 1192 + " because it has same value that was autofilled"); 1193 } 1194 continue; 1195 } 1196 1197 if (sDebug) { 1198 Slog.d(TAG, "logContextCommitted() found changed state: " + viewState); 1199 } 1200 if (changedFieldIds == null) { 1201 changedFieldIds = new ArrayList<>(); 1202 changedDatasetIds = new ArrayList<>(); 1203 } 1204 changedFieldIds.add(viewState.id); 1205 changedDatasetIds.add(datasetId); 1206 } else { 1207 final AutofillValue currentValue = viewState.getCurrentValue(); 1208 if (currentValue == null) { 1209 if (sDebug) { 1210 Slog.d(TAG, "logContextCommitted(): skipping view without current " 1211 + "value ( " + viewState + ")"); 1212 } 1213 continue; 1214 } 1215 // Check if value match a dataset. 1216 if (hasAtLeastOneDataset) { 1217 for (int j = 0; j < responseCount; j++) { 1218 final FillResponse response = mResponses.valueAt(j); 1219 final List<Dataset> datasets = response.getDatasets(); 1220 if (datasets == null || datasets.isEmpty()) { 1221 if (sVerbose) { 1222 Slog.v(TAG, "logContextCommitted() no datasets at " + j); 1223 } 1224 } else { 1225 for (int k = 0; k < datasets.size(); k++) { 1226 final Dataset dataset = datasets.get(k); 1227 final String datasetId = dataset.getId(); 1228 if (datasetId == null) { 1229 if (sVerbose) { 1230 Slog.v(TAG, "logContextCommitted() skipping idless " 1231 + "dataset " + dataset); 1232 } 1233 } else { 1234 final ArrayList<AutofillValue> values = 1235 dataset.getFieldValues(); 1236 for (int l = 0; l < values.size(); l++) { 1237 final AutofillValue candidate = values.get(l); 1238 if (currentValue.equals(candidate)) { 1239 if (sDebug) { 1240 Slog.d(TAG, "field " + viewState.id + " was " 1241 + "manually filled with value set by " 1242 + "dataset " + datasetId); 1243 } 1244 if (manuallyFilledIds == null) { 1245 manuallyFilledIds = new ArrayMap<>(); 1246 } 1247 ArraySet<String> datasetIds = 1248 manuallyFilledIds.get(viewState.id); 1249 if (datasetIds == null) { 1250 datasetIds = new ArraySet<>(1); 1251 manuallyFilledIds.put(viewState.id, datasetIds); 1252 } 1253 datasetIds.add(datasetId); 1254 } 1255 } // for l 1256 if (mSelectedDatasetIds == null 1257 || !mSelectedDatasetIds.contains(datasetId)) { 1258 if (sVerbose) { 1259 Slog.v(TAG, "adding ignored dataset " + datasetId); 1260 } 1261 if (ignoredDatasets == null) { 1262 ignoredDatasets = new ArraySet<>(); 1263 } 1264 ignoredDatasets.add(datasetId); 1265 } // if 1266 } // if 1267 } // for k 1268 } // else 1269 } // for j 1270 } 1271 1272 } // else 1273 } // else 1274 } 1275 1276 ArrayList<AutofillId> manuallyFilledFieldIds = null; 1277 ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null; 1278 1279 // Must "flatten" the map to the parcelable collection primitives 1280 if (manuallyFilledIds != null) { 1281 final int size = manuallyFilledIds.size(); 1282 manuallyFilledFieldIds = new ArrayList<>(size); 1283 manuallyFilledDatasetIds = new ArrayList<>(size); 1284 for (int i = 0; i < size; i++) { 1285 final AutofillId fieldId = manuallyFilledIds.keyAt(i); 1286 final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i); 1287 manuallyFilledFieldIds.add(fieldId); 1288 manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds)); 1289 } 1290 } 1291 1292 // Sets field classification scores 1293 final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy(); 1294 if (userData != null && fcStrategy != null) { 1295 logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds, 1296 changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, 1297 userData, mViewStates.values()); 1298 } else { 1299 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, 1300 ignoredDatasets, changedFieldIds, changedDatasetIds, 1301 manuallyFilledFieldIds, manuallyFilledDatasetIds, 1302 mComponentName, mCompatMode); 1303 } 1304 } 1305 1306 /** 1307 * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for 1308 * {@code fieldId} based on its {@code currentValue} and {@code userData}. 1309 */ 1310 private void logFieldClassificationScoreLocked( 1311 @NonNull FieldClassificationStrategy fcStrategy, 1312 @NonNull ArraySet<String> ignoredDatasets, 1313 @NonNull ArrayList<AutofillId> changedFieldIds, 1314 @NonNull ArrayList<String> changedDatasetIds, 1315 @NonNull ArrayList<AutofillId> manuallyFilledFieldIds, 1316 @NonNull ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 1317 @NonNull UserData userData, @NonNull Collection<ViewState> viewStates) { 1318 1319 final String[] userValues = userData.getValues(); 1320 final String[] categoryIds = userData.getCategoryIds(); 1321 1322 // Sanity check 1323 if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) { 1324 final int valuesLength = userValues == null ? -1 : userValues.length; 1325 final int idsLength = categoryIds == null ? -1 : categoryIds.length; 1326 Slog.w(TAG, "setScores(): user data mismatch: values.length = " 1327 + valuesLength + ", ids.length = " + idsLength); 1328 return; 1329 } 1330 1331 final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize(); 1332 1333 final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize); 1334 final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>( 1335 maxFieldsSize); 1336 1337 final String algorithm = userData.getFieldClassificationAlgorithm(); 1338 final Bundle algorithmArgs = userData.getAlgorithmArgs(); 1339 final int viewsSize = viewStates.size(); 1340 1341 // First, we get all scores. 1342 final AutofillId[] autofillIds = new AutofillId[viewsSize]; 1343 final ArrayList<AutofillValue> currentValues = new ArrayList<>(viewsSize); 1344 int k = 0; 1345 for (ViewState viewState : viewStates) { 1346 currentValues.add(viewState.getCurrentValue()); 1347 autofillIds[k++] = viewState.id; 1348 } 1349 1350 // Then use the results, asynchronously 1351 final RemoteCallback callback = new RemoteCallback((result) -> { 1352 if (result == null) { 1353 if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); 1354 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, 1355 ignoredDatasets, changedFieldIds, changedDatasetIds, 1356 manuallyFilledFieldIds, manuallyFilledDatasetIds, 1357 mComponentName, mCompatMode); 1358 return; 1359 } 1360 final Scores scores = result.getParcelable(EXTRA_SCORES); 1361 if (scores == null) { 1362 Slog.w(TAG, "No field classification score on " + result); 1363 return; 1364 } 1365 int i = 0, j = 0; 1366 try { 1367 // Iteract over all autofill fields first 1368 for (i = 0; i < viewsSize; i++) { 1369 final AutofillId autofillId = autofillIds[i]; 1370 1371 // Search the best scores for each category (as some categories could have 1372 // multiple user values 1373 ArrayMap<String, Float> scoresByField = null; 1374 for (j = 0; j < userValues.length; j++) { 1375 final String categoryId = categoryIds[j]; 1376 final float score = scores.scores[i][j]; 1377 if (score > 0) { 1378 if (scoresByField == null) { 1379 scoresByField = new ArrayMap<>(userValues.length); 1380 } 1381 final Float currentScore = scoresByField.get(categoryId); 1382 if (currentScore != null && currentScore > score) { 1383 if (sVerbose) { 1384 Slog.v(TAG, "skipping score " + score 1385 + " because it's less than " + currentScore); 1386 } 1387 continue; 1388 } 1389 if (sVerbose) { 1390 Slog.v(TAG, "adding score " + score + " at index " + j + " and id " 1391 + autofillId); 1392 } 1393 scoresByField.put(categoryId, score); 1394 } 1395 else if (sVerbose) { 1396 Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId); 1397 } 1398 } 1399 if (scoresByField == null) { 1400 if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId); 1401 continue; 1402 } 1403 1404 // Then create the matches for that autofill id 1405 final ArrayList<Match> matches = new ArrayList<>(scoresByField.size()); 1406 for (j = 0; j < scoresByField.size(); j++) { 1407 final String fieldId = scoresByField.keyAt(j); 1408 final float score = scoresByField.valueAt(j); 1409 matches.add(new Match(fieldId, score)); 1410 } 1411 detectedFieldIds.add(autofillId); 1412 detectedFieldClassifications.add(new FieldClassification(matches)); 1413 } // for i 1414 } catch (ArrayIndexOutOfBoundsException e) { 1415 wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); 1416 return; 1417 } 1418 1419 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, 1420 ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 1421 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, 1422 mComponentName, mCompatMode); 1423 }); 1424 1425 fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues); 1426 } 1427 1428 /** 1429 * Shows the save UI, when session can be saved. 1430 * 1431 * @return {@code true} if session is done, or {@code false} if it's pending user action. 1432 */ 1433 @GuardedBy("mLock") 1434 public boolean showSaveLocked() { 1435 if (mDestroyed) { 1436 Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: " 1437 + id + " destroyed"); 1438 return false; 1439 } 1440 final FillResponse response = getLastResponseLocked("showSaveLocked()"); 1441 final SaveInfo saveInfo = response == null ? null : response.getSaveInfo(); 1442 1443 /* 1444 * The Save dialog is only shown if all conditions below are met: 1445 * 1446 * - saveInfo is not null. 1447 * - autofillValue of all required ids is not null. 1448 * - autofillValue of at least one id (required or optional) has changed. 1449 * - there is no Dataset in the last FillResponse whose values of all dataset fields matches 1450 * the current values of all fields in the screen. 1451 */ 1452 if (saveInfo == null) { 1453 if (sVerbose) Slog.v(TAG, "showSaveLocked(): no saveInfo from service"); 1454 return true; 1455 } 1456 1457 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo); 1458 1459 // Cache used to make sure changed fields do not belong to a dataset. 1460 final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>(); 1461 final ArraySet<AutofillId> allIds = new ArraySet<>(); 1462 1463 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 1464 boolean allRequiredAreNotEmpty = true; 1465 boolean atLeastOneChanged = false; 1466 if (requiredIds != null) { 1467 for (int i = 0; i < requiredIds.length; i++) { 1468 final AutofillId id = requiredIds[i]; 1469 if (id == null) { 1470 Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds)); 1471 continue; 1472 } 1473 allIds.add(id); 1474 final ViewState viewState = mViewStates.get(id); 1475 if (viewState == null) { 1476 Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id); 1477 allRequiredAreNotEmpty = false; 1478 break; 1479 } 1480 1481 AutofillValue value = viewState.getCurrentValue(); 1482 if (value == null || value.isEmpty()) { 1483 final AutofillValue initialValue = getValueFromContextsLocked(id); 1484 if (initialValue != null) { 1485 if (sDebug) { 1486 Slog.d(TAG, "Value of required field " + id + " didn't change; " 1487 + "using initial value (" + initialValue + ") instead"); 1488 } 1489 value = initialValue; 1490 } else { 1491 if (sDebug) { 1492 Slog.d(TAG, "empty value for required " + id ); 1493 } 1494 allRequiredAreNotEmpty = false; 1495 break; 1496 } 1497 } 1498 1499 value = getSanitizedValue(sanitizers, id, value); 1500 if (value == null) { 1501 if (sDebug) { 1502 Slog.d(TAG, "value of required field " + id + " failed sanitization"); 1503 } 1504 allRequiredAreNotEmpty = false; 1505 break; 1506 } 1507 viewState.setSanitizedValue(value); 1508 currentValues.put(id, value); 1509 final AutofillValue filledValue = viewState.getAutofilledValue(); 1510 1511 if (!value.equals(filledValue)) { 1512 boolean changed = true; 1513 if (filledValue == null) { 1514 // Dataset was not autofilled, make sure initial value didn't change. 1515 final AutofillValue initialValue = getValueFromContextsLocked(id); 1516 if (initialValue != null && initialValue.equals(value)) { 1517 if (sDebug) { 1518 Slog.d(TAG, "id " + id + " is part of dataset but initial value " 1519 + "didn't change: " + value); 1520 } 1521 changed = false; 1522 } 1523 } 1524 if (changed) { 1525 if (sDebug) { 1526 Slog.d(TAG, "found a change on required " + id + ": " + filledValue 1527 + " => " + value); 1528 } 1529 atLeastOneChanged = true; 1530 } 1531 } 1532 } 1533 } 1534 1535 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 1536 if (allRequiredAreNotEmpty) { 1537 if (!atLeastOneChanged && optionalIds != null) { 1538 // No change on required ids yet, look for changes on optional ids. 1539 for (int i = 0; i < optionalIds.length; i++) { 1540 final AutofillId id = optionalIds[i]; 1541 allIds.add(id); 1542 final ViewState viewState = mViewStates.get(id); 1543 if (viewState == null) { 1544 Slog.w(TAG, "no ViewState for optional " + id); 1545 continue; 1546 } 1547 if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { 1548 final AutofillValue currentValue = viewState.getCurrentValue(); 1549 currentValues.put(id, currentValue); 1550 final AutofillValue filledValue = viewState.getAutofilledValue(); 1551 if (currentValue != null && !currentValue.equals(filledValue)) { 1552 if (sDebug) { 1553 Slog.d(TAG, "found a change on optional " + id + ": " + filledValue 1554 + " => " + currentValue); 1555 } 1556 atLeastOneChanged = true; 1557 break; 1558 } 1559 } else { 1560 // Update current values cache based on initial value 1561 final AutofillValue initialValue = getValueFromContextsLocked(id); 1562 if (sDebug) { 1563 Slog.d(TAG, "no current value for " + id + "; initial value is " 1564 + initialValue); 1565 } 1566 if (initialValue != null) { 1567 currentValues.put(id, initialValue); 1568 } 1569 } 1570 } 1571 } 1572 if (atLeastOneChanged) { 1573 if (sDebug) { 1574 Slog.d(TAG, "at least one field changed, validate fields for save UI"); 1575 } 1576 final InternalValidator validator = saveInfo.getValidator(); 1577 if (validator != null) { 1578 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION); 1579 boolean isValid; 1580 try { 1581 isValid = validator.isValid(this); 1582 if (sDebug) Slog.d(TAG, validator + " returned " + isValid); 1583 log.setType(isValid 1584 ? MetricsEvent.TYPE_SUCCESS 1585 : MetricsEvent.TYPE_DISMISS); 1586 } catch (Exception e) { 1587 Slog.e(TAG, "Not showing save UI because validation failed:", e); 1588 log.setType(MetricsEvent.TYPE_FAILURE); 1589 mMetricsLogger.write(log); 1590 return true; 1591 } 1592 1593 mMetricsLogger.write(log); 1594 if (!isValid) { 1595 Slog.i(TAG, "not showing save UI because fields failed validation"); 1596 return true; 1597 } 1598 } 1599 1600 // Make sure the service doesn't have the fields already by checking the datasets 1601 // content. 1602 final List<Dataset> datasets = response.getDatasets(); 1603 if (datasets != null) { 1604 datasets_loop: for (int i = 0; i < datasets.size(); i++) { 1605 final Dataset dataset = datasets.get(i); 1606 final ArrayMap<AutofillId, AutofillValue> datasetValues = 1607 Helper.getFields(dataset); 1608 if (sVerbose) { 1609 Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i 1610 + ": " + dataset + "; allIds=" + allIds); 1611 } 1612 for (int j = 0; j < allIds.size(); j++) { 1613 final AutofillId id = allIds.valueAt(j); 1614 final AutofillValue currentValue = currentValues.get(id); 1615 if (currentValue == null) { 1616 if (sDebug) { 1617 Slog.d(TAG, "dataset has value for field that is null: " + id); 1618 } 1619 continue datasets_loop; 1620 } 1621 final AutofillValue datasetValue = datasetValues.get(id); 1622 if (!currentValue.equals(datasetValue)) { 1623 if (sDebug) { 1624 Slog.d(TAG, "found a dataset change on id " + id + ": from " 1625 + datasetValue + " to " + currentValue); 1626 } 1627 continue datasets_loop; 1628 } 1629 if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id); 1630 } 1631 if (sDebug) { 1632 Slog.d(TAG, "ignoring Save UI because all fields match contents of " 1633 + "dataset #" + i + ": " + dataset); 1634 } 1635 return true; 1636 } 1637 } 1638 1639 if (sDebug) { 1640 Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " 1641 + id + "!"); 1642 } 1643 1644 // Use handler so logContextCommitted() is logged first 1645 mHandler.sendMessage(obtainMessage( 1646 Session::logSaveShown, this)); 1647 1648 final IAutoFillManagerClient client = getClient(); 1649 mPendingSaveUi = new PendingUi(mActivityToken, id, client); 1650 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(), 1651 mService.getServicePackageName(), saveInfo, this, 1652 mComponentName, this, mPendingSaveUi, mCompatMode); 1653 if (client != null) { 1654 try { 1655 client.setSaveUiState(id, true); 1656 } catch (RemoteException e) { 1657 Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e); 1658 } 1659 } 1660 mIsSaving = true; 1661 return false; 1662 } 1663 } 1664 // Nothing changed... 1665 if (sDebug) { 1666 Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities." 1667 + "allRequiredAreNotNull=" + allRequiredAreNotEmpty 1668 + ", atLeastOneChanged=" + atLeastOneChanged); 1669 } 1670 return true; 1671 } 1672 1673 private void logSaveShown() { 1674 mService.logSaveShown(id, mClientState); 1675 } 1676 1677 @Nullable 1678 private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) { 1679 if (saveInfo == null) return null; 1680 1681 final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys(); 1682 if (sanitizerKeys == null) return null; 1683 1684 final int size = sanitizerKeys.length ; 1685 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size); 1686 if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers"); 1687 final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues(); 1688 for (int i = 0; i < size; i++) { 1689 final InternalSanitizer sanitizer = sanitizerKeys[i]; 1690 final AutofillId[] ids = sanitizerValues[i]; 1691 if (sDebug) { 1692 Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids " 1693 + Arrays.toString(ids)); 1694 } 1695 for (AutofillId id : ids) { 1696 sanitizers.put(id, sanitizer); 1697 } 1698 } 1699 return sanitizers; 1700 } 1701 1702 @Nullable 1703 private AutofillValue getSanitizedValue( 1704 @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers, 1705 @NonNull AutofillId id, 1706 @NonNull AutofillValue value) { 1707 if (sanitizers == null) return value; 1708 1709 final InternalSanitizer sanitizer = sanitizers.get(id); 1710 if (sanitizer == null) { 1711 return value; 1712 } 1713 1714 final AutofillValue sanitized = sanitizer.sanitize(value); 1715 if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized); 1716 return sanitized; 1717 } 1718 1719 /** 1720 * Returns whether the session is currently showing the save UI 1721 */ 1722 @GuardedBy("mLock") 1723 boolean isSavingLocked() { 1724 return mIsSaving; 1725 } 1726 1727 /** 1728 * Gets the latest non-empty value for the given id in the autofill contexts. 1729 */ 1730 @GuardedBy("mLock") 1731 @Nullable 1732 private AutofillValue getValueFromContextsLocked(AutofillId id) { 1733 final int numContexts = mContexts.size(); 1734 for (int i = numContexts - 1; i >= 0; i--) { 1735 final FillContext context = mContexts.get(i); 1736 final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), id); 1737 if (node != null) { 1738 final AutofillValue value = node.getAutofillValue(); 1739 if (sDebug) { 1740 Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + value); 1741 } 1742 if (value != null && !value.isEmpty()) { 1743 return value; 1744 } 1745 } 1746 } 1747 return null; 1748 } 1749 1750 /** 1751 * Gets the latest autofill options for the given id in the autofill contexts. 1752 */ 1753 @GuardedBy("mLock") 1754 @Nullable 1755 private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) { 1756 final int numContexts = mContexts.size(); 1757 1758 for (int i = numContexts - 1; i >= 0; i--) { 1759 final FillContext context = mContexts.get(i); 1760 final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), id); 1761 if (node != null && node.getAutofillOptions() != null) { 1762 return node.getAutofillOptions(); 1763 } 1764 } 1765 return null; 1766 } 1767 1768 /** 1769 * Calls service when user requested save. 1770 */ 1771 @GuardedBy("mLock") 1772 void callSaveLocked() { 1773 if (mDestroyed) { 1774 Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " 1775 + id + " destroyed"); 1776 return; 1777 } 1778 1779 if (sVerbose) Slog.v(TAG, "callSaveLocked(): mViewStates=" + mViewStates); 1780 1781 if (mContexts == null) { 1782 Slog.w(TAG, "callSaveLocked(): no contexts"); 1783 return; 1784 } 1785 1786 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = 1787 createSanitizers(getSaveInfoLocked()); 1788 1789 final int numContexts = mContexts.size(); 1790 1791 for (int contextNum = 0; contextNum < numContexts; contextNum++) { 1792 final FillContext context = mContexts.get(contextNum); 1793 1794 final ViewNode[] nodes = 1795 context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); 1796 1797 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context); 1798 1799 for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) { 1800 final ViewState viewState = mViewStates.valueAt(viewStateNum); 1801 1802 final AutofillId id = viewState.id; 1803 final AutofillValue value = viewState.getCurrentValue(); 1804 if (value == null) { 1805 if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id); 1806 continue; 1807 } 1808 final ViewNode node = nodes[viewStateNum]; 1809 if (node == null) { 1810 Slog.w(TAG, "callSaveLocked(): did not find node with id " + id); 1811 continue; 1812 } 1813 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value); 1814 1815 AutofillValue sanitizedValue = viewState.getSanitizedValue(); 1816 1817 if (sanitizedValue == null) { 1818 // Field is optional and haven't been sanitized yet. 1819 sanitizedValue = getSanitizedValue(sanitizers, id, value); 1820 } 1821 if (sanitizedValue != null) { 1822 node.updateAutofillValue(sanitizedValue); 1823 } else if (sDebug) { 1824 Slog.d(TAG, "Not updating field " + id + " because it failed sanitization"); 1825 } 1826 } 1827 1828 // Sanitize structure before it's sent to service. 1829 context.getStructure().sanitizeForParceling(false); 1830 1831 if (sVerbose) { 1832 Slog.v(TAG, "Dumping structure of " + context + " before calling service.save()"); 1833 context.getStructure().dump(false); 1834 } 1835 } 1836 1837 // Remove pending fill requests as the session is finished. 1838 cancelCurrentRequestLocked(); 1839 1840 // Dispatch a snapshot of the current contexts list since it may change 1841 // until the dispatch happens. The items in the list don't need to be cloned 1842 // since we don't hold on them anywhere else. The client state is not touched 1843 // by us, so no need to copy. 1844 final SaveRequest saveRequest = new SaveRequest(new ArrayList<>(mContexts), mClientState, 1845 mSelectedDatasetIds); 1846 mRemoteFillService.onSaveRequest(saveRequest); 1847 } 1848 1849 /** 1850 * Starts (if necessary) a new fill request upon entering a view. 1851 * 1852 * <p>A new request will be started in 2 scenarios: 1853 * <ol> 1854 * <li>If the user manually requested autofill. 1855 * <li>If the view is part of a new partition. 1856 * </ol> 1857 * 1858 * @param id The id of the view that is entered. 1859 * @param viewState The view that is entered. 1860 * @param flags The flag that was passed by the AutofillManager. 1861 */ 1862 @GuardedBy("mLock") 1863 private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, 1864 @NonNull ViewState viewState, int flags) { 1865 if ((flags & FLAG_MANUAL_REQUEST) != 0) { 1866 if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); 1867 viewState.setState(STATE_RESTARTED_SESSION); 1868 requestNewFillResponseLocked(flags); 1869 return; 1870 } 1871 1872 // If it's not, then check if it it should start a partition. 1873 if (shouldStartNewPartitionLocked(id)) { 1874 if (sDebug) { 1875 Slog.d(TAG, "Starting partition for view id " + id + ": " 1876 + viewState.getStateAsString()); 1877 } 1878 viewState.setState(ViewState.STATE_STARTED_PARTITION); 1879 requestNewFillResponseLocked(flags); 1880 } else { 1881 if (sVerbose) { 1882 Slog.v(TAG, "Not starting new partition for view " + id + ": " 1883 + viewState.getStateAsString()); 1884 } 1885 } 1886 } 1887 1888 /** 1889 * Determines if a new partition should be started for an id. 1890 * 1891 * @param id The id of the view that is entered 1892 * 1893 * @return {@code true} iff a new partition should be started 1894 */ 1895 @GuardedBy("mLock") 1896 private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { 1897 if (mResponses == null) { 1898 return true; 1899 } 1900 1901 final int numResponses = mResponses.size(); 1902 if (numResponses >= sPartitionMaxCount) { 1903 Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id 1904 + " reached maximum of " + sPartitionMaxCount); 1905 return false; 1906 } 1907 1908 for (int responseNum = 0; responseNum < numResponses; responseNum++) { 1909 final FillResponse response = mResponses.valueAt(responseNum); 1910 1911 if (ArrayUtils.contains(response.getIgnoredIds(), id)) { 1912 return false; 1913 } 1914 1915 final SaveInfo saveInfo = response.getSaveInfo(); 1916 if (saveInfo != null) { 1917 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id) 1918 || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) { 1919 return false; 1920 } 1921 } 1922 1923 final List<Dataset> datasets = response.getDatasets(); 1924 if (datasets != null) { 1925 final int numDatasets = datasets.size(); 1926 1927 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) { 1928 final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds(); 1929 1930 if (fields != null && fields.contains(id)) { 1931 return false; 1932 } 1933 } 1934 } 1935 1936 if (ArrayUtils.contains(response.getAuthenticationIds(), id)) { 1937 return false; 1938 } 1939 } 1940 1941 return true; 1942 } 1943 1944 @GuardedBy("mLock") 1945 void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, 1946 int flags) { 1947 if (mDestroyed) { 1948 Slog.w(TAG, "Call to Session#updateLocked() rejected - session: " 1949 + id + " destroyed"); 1950 return; 1951 } 1952 if (sVerbose) { 1953 Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + actionAsString(action) 1954 + ", flags=" + flags); 1955 } 1956 ViewState viewState = mViewStates.get(id); 1957 1958 if (viewState == null) { 1959 if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED 1960 || action == ACTION_VIEW_ENTERED) { 1961 if (sVerbose) Slog.v(TAG, "Creating viewState for " + id); 1962 boolean isIgnored = isIgnoredLocked(id); 1963 viewState = new ViewState(this, id, this, 1964 isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL); 1965 mViewStates.put(id, viewState); 1966 1967 // TODO(b/73648631): for optimization purposes, should also ignore if change is 1968 // detectable, and batch-send them when the session is finished (but that will 1969 // require tracking detectable fields on AutofillManager) 1970 if (isIgnored) { 1971 if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + viewState); 1972 return; 1973 } 1974 } else { 1975 if (sVerbose) Slog.v(TAG, "Ignoring specific action when viewState=null"); 1976 return; 1977 } 1978 } 1979 1980 switch(action) { 1981 case ACTION_START_SESSION: 1982 // View is triggering autofill. 1983 mCurrentViewId = viewState.id; 1984 viewState.update(value, virtualBounds, flags); 1985 viewState.setState(ViewState.STATE_STARTED_SESSION); 1986 requestNewFillResponseLocked(flags); 1987 break; 1988 case ACTION_VALUE_CHANGED: 1989 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { 1990 // Must cancel the session if the value of the URL bar changed 1991 final String currentUrl = mUrlBar == null ? null 1992 : mUrlBar.getText().toString().trim(); 1993 if (currentUrl == null) { 1994 // Sanity check - shouldn't happen. 1995 wtf(null, "URL bar value changed, but current value is null"); 1996 return; 1997 } 1998 if (value == null || ! value.isText()) { 1999 // Sanity check - shouldn't happen. 2000 wtf(null, "URL bar value changed to null or non-text: %s", value); 2001 return; 2002 } 2003 final String newUrl = value.getTextValue().toString(); 2004 if (newUrl.equals(currentUrl)) { 2005 if (sDebug) Slog.d(TAG, "Ignoring change on URL bar as it's the same"); 2006 return; 2007 } 2008 if (mSaveOnAllViewsInvisible) { 2009 // We cannot cancel the session because it could hinder Save when all views 2010 // are finished, as the URL bar changed callback is usually called before 2011 // the virtual views become invisible. 2012 if (sDebug) { 2013 Slog.d(TAG, "Ignoring change on URL because session will finish when " 2014 + "views are gone"); 2015 } 2016 return; 2017 } 2018 if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed"); 2019 forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE); 2020 return; 2021 } 2022 2023 if (!Objects.equals(value, viewState.getCurrentValue())) { 2024 if ((value == null || value.isEmpty()) 2025 && viewState.getCurrentValue() != null 2026 && viewState.getCurrentValue().isText() 2027 && viewState.getCurrentValue().getTextValue() != null 2028 && getSaveInfoLocked() != null) { 2029 final int length = viewState.getCurrentValue().getTextValue().length(); 2030 if (sDebug) { 2031 Slog.d(TAG, "updateLocked(" + id + "): resetting value that was " 2032 + length + " chars long"); 2033 } 2034 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) 2035 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); 2036 mMetricsLogger.write(log); 2037 } 2038 2039 // Always update the internal state. 2040 viewState.setCurrentValue(value); 2041 2042 // Must check if this update was caused by autofilling the view, in which 2043 // case we just update the value, but not the UI. 2044 final AutofillValue filledValue = viewState.getAutofilledValue(); 2045 if (filledValue != null && filledValue.equals(value)) { 2046 if (sVerbose) { 2047 Slog.v(TAG, "ignoring autofilled change on id " + id); 2048 } 2049 return; 2050 } 2051 // Update the internal state... 2052 viewState.setState(ViewState.STATE_CHANGED); 2053 2054 //..and the UI 2055 final String filterText; 2056 if (value == null || !value.isText()) { 2057 filterText = null; 2058 } else { 2059 final CharSequence text = value.getTextValue(); 2060 // Text should never be null, but it doesn't hurt to check to avoid a 2061 // system crash... 2062 filterText = (text == null) ? null : text.toString(); 2063 } 2064 getUiForShowing().filterFillUi(filterText, this); 2065 } 2066 break; 2067 case ACTION_VIEW_ENTERED: 2068 if (sVerbose && virtualBounds != null) { 2069 Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds); 2070 } 2071 2072 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { 2073 if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")"); 2074 return; 2075 } 2076 2077 requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); 2078 2079 // Remove the UI if the ViewState has changed. 2080 if (mCurrentViewId != viewState.id) { 2081 mUi.hideFillUi(this); 2082 mCurrentViewId = viewState.id; 2083 } 2084 2085 // If the ViewState is ready to be displayed, onReady() will be called. 2086 viewState.update(value, virtualBounds, flags); 2087 break; 2088 case ACTION_VIEW_EXITED: 2089 if (mCurrentViewId == viewState.id) { 2090 if (sVerbose) Slog.d(TAG, "Exiting view " + id); 2091 mUi.hideFillUi(this); 2092 mCurrentViewId = null; 2093 } 2094 break; 2095 default: 2096 Slog.w(TAG, "updateLocked(): unknown action: " + action); 2097 } 2098 } 2099 2100 /** 2101 * Checks whether a view should be ignored. 2102 */ 2103 @GuardedBy("mLock") 2104 private boolean isIgnoredLocked(AutofillId id) { 2105 // Always check the latest response only 2106 final FillResponse response = getLastResponseLocked(null); 2107 if (response == null) return false; 2108 2109 return ArrayUtils.contains(response.getIgnoredIds(), id); 2110 } 2111 2112 @Override 2113 public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId, 2114 @Nullable AutofillValue value) { 2115 synchronized (mLock) { 2116 if (mDestroyed) { 2117 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: " 2118 + id + " destroyed"); 2119 return; 2120 } 2121 } 2122 2123 String filterText = null; 2124 if (value != null && value.isText()) { 2125 filterText = value.getTextValue().toString(); 2126 } 2127 2128 getUiForShowing().showFillUi(filledId, response, filterText, 2129 mService.getServicePackageName(), mComponentName, 2130 mService.getServiceLabel(), mService.getServiceIcon(), this, id, mCompatMode); 2131 2132 synchronized (mLock) { 2133 if (mUiShownTime == 0) { 2134 // Log first time UI is shown. 2135 mUiShownTime = SystemClock.elapsedRealtime(); 2136 final long duration = mUiShownTime - mStartTime; 2137 if (sDebug) { 2138 final StringBuilder msg = new StringBuilder("1st UI for ") 2139 .append(mActivityToken) 2140 .append(" shown in "); 2141 TimeUtils.formatDuration(duration, msg); 2142 Slog.d(TAG, msg.toString()); 2143 } 2144 final StringBuilder historyLog = new StringBuilder("id=").append(id) 2145 .append(" app=").append(mActivityToken) 2146 .append(" svc=").append(mService.getServicePackageName()) 2147 .append(" latency="); 2148 TimeUtils.formatDuration(duration, historyLog); 2149 mUiLatencyHistory.log(historyLog.toString()); 2150 2151 addTaggedDataToRequestLogLocked(response.getRequestId(), 2152 MetricsEvent.FIELD_AUTOFILL_DURATION, duration); 2153 } 2154 } 2155 } 2156 2157 boolean isDestroyed() { 2158 synchronized (mLock) { 2159 return mDestroyed; 2160 } 2161 } 2162 2163 IAutoFillManagerClient getClient() { 2164 synchronized (mLock) { 2165 return mClient; 2166 } 2167 } 2168 2169 private void notifyUnavailableToClient(int sessionFinishedState) { 2170 synchronized (mLock) { 2171 if (mCurrentViewId == null) return; 2172 try { 2173 if (mHasCallback) { 2174 mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState); 2175 } else if (sessionFinishedState != 0) { 2176 mClient.setSessionFinished(sessionFinishedState); 2177 } 2178 } catch (RemoteException e) { 2179 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e); 2180 } 2181 } 2182 } 2183 2184 @GuardedBy("mLock") 2185 private void updateTrackedIdsLocked() { 2186 // Only track the views of the last response as only those are reported back to the 2187 // service, see #showSaveLocked 2188 final FillResponse response = getLastResponseLocked(null); 2189 if (response == null) return; 2190 2191 ArraySet<AutofillId> trackedViews = null; 2192 mSaveOnAllViewsInvisible = false; 2193 boolean saveOnFinish = true; 2194 final SaveInfo saveInfo = response.getSaveInfo(); 2195 final AutofillId saveTriggerId; 2196 if (saveInfo != null) { 2197 saveTriggerId = saveInfo.getTriggerId(); 2198 if (saveTriggerId != null) { 2199 writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION); 2200 } 2201 mSaveOnAllViewsInvisible = 2202 (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; 2203 2204 // We only need to track views if we want to save once they become invisible. 2205 if (mSaveOnAllViewsInvisible) { 2206 if (trackedViews == null) { 2207 trackedViews = new ArraySet<>(); 2208 } 2209 if (saveInfo.getRequiredIds() != null) { 2210 Collections.addAll(trackedViews, saveInfo.getRequiredIds()); 2211 } 2212 2213 if (saveInfo.getOptionalIds() != null) { 2214 Collections.addAll(trackedViews, saveInfo.getOptionalIds()); 2215 } 2216 } 2217 if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { 2218 saveOnFinish = false; 2219 } 2220 2221 } else { 2222 saveTriggerId = null; 2223 } 2224 2225 // Must also track that are part of datasets, otherwise the FillUI won't be hidden when 2226 // they go away (if they're not savable). 2227 2228 final List<Dataset> datasets = response.getDatasets(); 2229 ArraySet<AutofillId> fillableIds = null; 2230 if (datasets != null) { 2231 for (int i = 0; i < datasets.size(); i++) { 2232 final Dataset dataset = datasets.get(i); 2233 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds(); 2234 if (fieldIds == null) continue; 2235 2236 for (int j = 0; j < fieldIds.size(); j++) { 2237 final AutofillId id = fieldIds.get(j); 2238 if (trackedViews == null || !trackedViews.contains(id)) { 2239 fillableIds = ArrayUtils.add(fillableIds, id); 2240 } 2241 } 2242 } 2243 } 2244 2245 try { 2246 if (sVerbose) { 2247 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds 2248 + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish); 2249 } 2250 mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, 2251 saveOnFinish, toArray(fillableIds), saveTriggerId); 2252 } catch (RemoteException e) { 2253 Slog.w(TAG, "Cannot set tracked ids", e); 2254 } 2255 } 2256 2257 /** 2258 * Sets the state of views that failed to autofill. 2259 */ 2260 @GuardedBy("mLock") 2261 void setAutofillFailureLocked(@NonNull List<AutofillId> ids) { 2262 for (int i = 0; i < ids.size(); i++) { 2263 final AutofillId id = ids.get(i); 2264 final ViewState viewState = mViewStates.get(id); 2265 if (viewState == null) { 2266 Slog.w(TAG, "setAutofillFailure(): no view for id " + id); 2267 continue; 2268 } 2269 viewState.resetState(ViewState.STATE_AUTOFILLED); 2270 final int state = viewState.getState(); 2271 viewState.setState(state | ViewState.STATE_AUTOFILL_FAILED); 2272 if (sVerbose) { 2273 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString()); 2274 } 2275 } 2276 } 2277 2278 @GuardedBy("mLock") 2279 private void replaceResponseLocked(@NonNull FillResponse oldResponse, 2280 @NonNull FillResponse newResponse, @Nullable Bundle newClientState) { 2281 // Disassociate view states with the old response 2282 setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true); 2283 // Move over the id 2284 newResponse.setRequestId(oldResponse.getRequestId()); 2285 // Replace the old response 2286 mResponses.put(newResponse.getRequestId(), newResponse); 2287 // Now process the new response 2288 processResponseLocked(newResponse, newClientState, 0); 2289 } 2290 2291 private void processNullResponseLocked(int flags) { 2292 if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null"); 2293 if ((flags & FLAG_MANUAL_REQUEST) != 0) { 2294 getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this); 2295 } 2296 mService.resetLastResponse(); 2297 // Nothing to be done, but need to notify client. 2298 notifyUnavailableToClient(AutofillManager.STATE_FINISHED); 2299 removeSelf(); 2300 } 2301 2302 @GuardedBy("mLock") 2303 private void processResponseLocked(@NonNull FillResponse newResponse, 2304 @Nullable Bundle newClientState, int flags) { 2305 // Make sure we are hiding the UI which will be shown 2306 // only if handling the current response requires it. 2307 mUi.hideAll(this); 2308 2309 final int requestId = newResponse.getRequestId(); 2310 if (sVerbose) { 2311 Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId 2312 + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse 2313 + ",newClientState=" + newClientState); 2314 } 2315 2316 if (mResponses == null) { 2317 // Set initial capacity as 2 to handle cases where service always requires auth. 2318 // TODO: add a metric for number of responses set by server, so we can use its average 2319 // as the initial array capacitiy. 2320 mResponses = new SparseArray<>(2); 2321 } 2322 mResponses.put(requestId, newResponse); 2323 mClientState = newClientState != null ? newClientState : newResponse.getClientState(); 2324 2325 setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false); 2326 updateTrackedIdsLocked(); 2327 2328 if (mCurrentViewId == null) { 2329 return; 2330 } 2331 2332 // Updates the UI, if necessary. 2333 final ViewState currentView = mViewStates.get(mCurrentViewId); 2334 currentView.maybeCallOnFillReady(flags); 2335 } 2336 2337 /** 2338 * Sets the state of all views in the given response. 2339 */ 2340 @GuardedBy("mLock") 2341 private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) { 2342 final List<Dataset> datasets = response.getDatasets(); 2343 if (datasets != null) { 2344 for (int i = 0; i < datasets.size(); i++) { 2345 final Dataset dataset = datasets.get(i); 2346 if (dataset == null) { 2347 Slog.w(TAG, "Ignoring null dataset on " + datasets); 2348 continue; 2349 } 2350 setViewStatesLocked(response, dataset, state, clearResponse); 2351 } 2352 } else if (response.getAuthentication() != null) { 2353 for (AutofillId autofillId : response.getAuthenticationIds()) { 2354 final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null); 2355 if (!clearResponse) { 2356 viewState.setResponse(response); 2357 } else { 2358 viewState.setResponse(null); 2359 } 2360 } 2361 } 2362 final SaveInfo saveInfo = response.getSaveInfo(); 2363 if (saveInfo != null) { 2364 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 2365 if (requiredIds != null) { 2366 for (AutofillId id : requiredIds) { 2367 createOrUpdateViewStateLocked(id, state, null); 2368 } 2369 } 2370 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 2371 if (optionalIds != null) { 2372 for (AutofillId id : optionalIds) { 2373 createOrUpdateViewStateLocked(id, state, null); 2374 } 2375 } 2376 } 2377 2378 final AutofillId[] authIds = response.getAuthenticationIds(); 2379 if (authIds != null) { 2380 for (AutofillId id : authIds) { 2381 createOrUpdateViewStateLocked(id, state, null); 2382 } 2383 } 2384 } 2385 2386 /** 2387 * Sets the state of all views in the given dataset and response. 2388 */ 2389 @GuardedBy("mLock") 2390 private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset, 2391 int state, boolean clearResponse) { 2392 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 2393 final ArrayList<AutofillValue> values = dataset.getFieldValues(); 2394 for (int j = 0; j < ids.size(); j++) { 2395 final AutofillId id = ids.get(j); 2396 final AutofillValue value = values.get(j); 2397 final ViewState viewState = createOrUpdateViewStateLocked(id, state, value); 2398 final String datasetId = dataset.getId(); 2399 if (datasetId != null) { 2400 viewState.setDatasetId(datasetId); 2401 } 2402 if (response != null) { 2403 viewState.setResponse(response); 2404 } else if (clearResponse) { 2405 viewState.setResponse(null); 2406 } 2407 } 2408 } 2409 2410 @GuardedBy("mLock") 2411 private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state, 2412 @Nullable AutofillValue value) { 2413 ViewState viewState = mViewStates.get(id); 2414 if (viewState != null) { 2415 viewState.setState(state); 2416 } else { 2417 viewState = new ViewState(this, id, this, state); 2418 if (sVerbose) { 2419 Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state); 2420 } 2421 mViewStates.put(id, viewState); 2422 } 2423 if ((state & ViewState.STATE_AUTOFILLED) != 0) { 2424 viewState.setAutofilledValue(value); 2425 } 2426 return viewState; 2427 } 2428 2429 void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) { 2430 if (sDebug) { 2431 Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex 2432 + "; dataset=" + dataset); 2433 } 2434 synchronized (mLock) { 2435 if (mDestroyed) { 2436 Slog.w(TAG, "Call to Session#autoFill() rejected - session: " 2437 + id + " destroyed"); 2438 return; 2439 } 2440 // Autofill it directly... 2441 if (dataset.getAuthentication() == null) { 2442 if (generateEvent) { 2443 mService.logDatasetSelected(dataset.getId(), id, mClientState); 2444 } 2445 2446 autoFillApp(dataset); 2447 return; 2448 } 2449 2450 // ...or handle authentication. 2451 mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState); 2452 setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); 2453 final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState); 2454 if (fillInIntent == null) { 2455 forceRemoveSelfLocked(); 2456 return; 2457 } 2458 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, 2459 datasetIndex); 2460 startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent); 2461 2462 } 2463 } 2464 2465 CharSequence getServiceName() { 2466 synchronized (mLock) { 2467 return mService.getServiceName(); 2468 } 2469 } 2470 2471 // TODO: this should never be null, but we got at least one occurrence, probably due to a race. 2472 @GuardedBy("mLock") 2473 @Nullable 2474 private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) { 2475 final Intent fillInIntent = new Intent(); 2476 2477 final FillContext context = getFillContextByRequestIdLocked(requestId); 2478 2479 if (context == null) { 2480 wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s", 2481 requestId, mContexts); 2482 return null; 2483 } 2484 fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure()); 2485 fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras); 2486 return fillInIntent; 2487 } 2488 2489 private void startAuthentication(int authenticationId, IntentSender intent, 2490 Intent fillInIntent) { 2491 try { 2492 synchronized (mLock) { 2493 mClient.authenticate(id, authenticationId, intent, fillInIntent); 2494 } 2495 } catch (RemoteException e) { 2496 Slog.e(TAG, "Error launching auth intent", e); 2497 } 2498 } 2499 2500 @Override 2501 public String toString() { 2502 return "Session: [id=" + id + ", component=" + mComponentName + "]"; 2503 } 2504 2505 @GuardedBy("mLock") 2506 void dumpLocked(String prefix, PrintWriter pw) { 2507 final String prefix2 = prefix + " "; 2508 pw.print(prefix); pw.print("id: "); pw.println(id); 2509 pw.print(prefix); pw.print("uid: "); pw.println(uid); 2510 pw.print(prefix); pw.print("flags: "); pw.println(mFlags); 2511 pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); 2512 pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); 2513 pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); 2514 pw.print(prefix); pw.print("Time to show UI: "); 2515 if (mUiShownTime == 0) { 2516 pw.println("N/A"); 2517 } else { 2518 TimeUtils.formatDuration(mUiShownTime - mStartTime, pw); 2519 pw.println(); 2520 } 2521 final int requestLogsSizes = mRequestLogs.size(); 2522 pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes); 2523 for (int i = 0; i < requestLogsSizes; i++) { 2524 final int requestId = mRequestLogs.keyAt(i); 2525 final LogMaker log = mRequestLogs.valueAt(i); 2526 pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req="); 2527 pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println(); 2528 } 2529 pw.print(prefix); pw.print("mResponses: "); 2530 if (mResponses == null) { 2531 pw.println("null"); 2532 } else { 2533 pw.println(mResponses.size()); 2534 for (int i = 0; i < mResponses.size(); i++) { 2535 pw.print(prefix2); pw.print('#'); pw.print(i); 2536 pw.print(' '); pw.println(mResponses.valueAt(i)); 2537 } 2538 } 2539 pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); 2540 pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); 2541 pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving); 2542 pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi); 2543 final int numberViews = mViewStates.size(); 2544 pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); 2545 for (int i = 0; i < numberViews; i++) { 2546 pw.print(prefix); pw.print("ViewState at #"); pw.println(i); 2547 mViewStates.valueAt(i).dump(prefix2, pw); 2548 } 2549 2550 pw.print(prefix); pw.print("mContexts: " ); 2551 if (mContexts != null) { 2552 int numContexts = mContexts.size(); 2553 for (int i = 0; i < numContexts; i++) { 2554 FillContext context = mContexts.get(i); 2555 2556 pw.print(prefix2); pw.print(context); 2557 if (sVerbose) { 2558 pw.println("AssistStructure dumped at logcat)"); 2559 2560 // TODO: add method on AssistStructure to dump on pw 2561 context.getStructure().dump(false); 2562 } 2563 } 2564 } else { 2565 pw.println("null"); 2566 } 2567 2568 pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); 2569 if (mClientState != null) { 2570 pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw 2571 .println(" bytes"); 2572 } 2573 pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode); 2574 pw.print(prefix); pw.print("mUrlBar: "); 2575 if (mUrlBar == null) { 2576 pw.println("N/A"); 2577 } else { 2578 pw.print("id="); pw.print(mUrlBar.getAutofillId()); 2579 pw.print(" domain="); pw.print(mUrlBar.getWebDomain()); 2580 pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText()); 2581 } 2582 pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( 2583 mSaveOnAllViewsInvisible); 2584 pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); 2585 mRemoteFillService.dump(prefix, pw); 2586 } 2587 2588 private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) { 2589 pw.print("CAT="); pw.print(log.getCategory()); 2590 pw.print(", TYPE="); 2591 final int type = log.getType(); 2592 switch (type) { 2593 case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break; 2594 case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break; 2595 case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break; 2596 default: pw.print("UNSUPPORTED"); 2597 } 2598 pw.print('('); pw.print(type); pw.print(')'); 2599 pw.print(", PKG="); pw.print(log.getPackageName()); 2600 pw.print(", SERVICE="); pw.print(log 2601 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE)); 2602 pw.print(", ORDINAL="); pw.print(log 2603 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL)); 2604 dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS); 2605 dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS); 2606 dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION); 2607 final int authStatus = 2608 getNumericValue(log, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS); 2609 if (authStatus != 0) { 2610 pw.print(", AUTH_STATUS="); 2611 switch (authStatus) { 2612 case MetricsEvent.AUTOFILL_AUTHENTICATED: 2613 pw.print("AUTHENTICATED"); break; 2614 case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED: 2615 pw.print("DATASET_AUTHENTICATED"); break; 2616 case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION: 2617 pw.print("INVALID_AUTHENTICATION"); break; 2618 case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION: 2619 pw.print("INVALID_DATASET_AUTHENTICATION"); break; 2620 default: pw.print("UNSUPPORTED"); 2621 } 2622 pw.print('('); pw.print(authStatus); pw.print(')'); 2623 } 2624 dumpNumericValue(pw, log, "FC_IDS", 2625 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS); 2626 dumpNumericValue(pw, log, "COMPAT_MODE", 2627 MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE); 2628 } 2629 2630 private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log, 2631 @NonNull String field, int tag) { 2632 final int value = getNumericValue(log, tag); 2633 if (value != 0) { 2634 pw.print(", "); pw.print(field); pw.print('='); pw.print(value); 2635 } 2636 } 2637 2638 void autoFillApp(Dataset dataset) { 2639 synchronized (mLock) { 2640 if (mDestroyed) { 2641 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: " 2642 + id + " destroyed"); 2643 return; 2644 } 2645 try { 2646 // Skip null values as a null values means no change 2647 final int entryCount = dataset.getFieldIds().size(); 2648 final List<AutofillId> ids = new ArrayList<>(entryCount); 2649 final List<AutofillValue> values = new ArrayList<>(entryCount); 2650 boolean waitingDatasetAuth = false; 2651 for (int i = 0; i < entryCount; i++) { 2652 if (dataset.getFieldValues().get(i) == null) { 2653 continue; 2654 } 2655 final AutofillId viewId = dataset.getFieldIds().get(i); 2656 ids.add(viewId); 2657 values.add(dataset.getFieldValues().get(i)); 2658 final ViewState viewState = mViewStates.get(viewId); 2659 if (viewState != null 2660 && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) { 2661 if (sVerbose) { 2662 Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth"); 2663 } 2664 waitingDatasetAuth = true; 2665 viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH); 2666 } 2667 } 2668 if (!ids.isEmpty()) { 2669 if (waitingDatasetAuth) { 2670 mUi.hideFillUi(this); 2671 } 2672 if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); 2673 2674 mClient.autofill(id, ids, values); 2675 if (dataset.getId() != null) { 2676 if (mSelectedDatasetIds == null) { 2677 mSelectedDatasetIds = new ArrayList<>(); 2678 } 2679 mSelectedDatasetIds.add(dataset.getId()); 2680 } 2681 setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false); 2682 } 2683 } catch (RemoteException e) { 2684 Slog.w(TAG, "Error autofilling activity: " + e); 2685 } 2686 } 2687 } 2688 2689 private AutoFillUI getUiForShowing() { 2690 synchronized (mLock) { 2691 mUi.setCallback(this); 2692 return mUi; 2693 } 2694 } 2695 2696 /** 2697 * Cleans up this session. 2698 * 2699 * <p>Typically called in 2 scenarios: 2700 * 2701 * <ul> 2702 * <li>When the session naturally finishes (i.e., from {@link #removeSelfLocked()}. 2703 * <li>When the service hosting the session is finished (for example, because the user 2704 * disabled it). 2705 * </ul> 2706 */ 2707 @GuardedBy("mLock") 2708 RemoteFillService destroyLocked() { 2709 if (mDestroyed) { 2710 return null; 2711 } 2712 unlinkClientVultureLocked(); 2713 mUi.destroyAll(mPendingSaveUi, this, true); 2714 mUi.clearCallback(this); 2715 mDestroyed = true; 2716 2717 // Log metrics 2718 final int totalRequests = mRequestLogs.size(); 2719 if (totalRequests > 0) { 2720 if (sVerbose) Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " requests"); 2721 for (int i = 0; i < totalRequests; i++) { 2722 final LogMaker log = mRequestLogs.valueAt(i); 2723 mMetricsLogger.write(log); 2724 } 2725 } 2726 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) 2727 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests)); 2728 2729 return mRemoteFillService; 2730 } 2731 2732 /** 2733 * Cleans up this session and remove it from the service always, even if it does have a pending 2734 * Save UI. 2735 */ 2736 @GuardedBy("mLock") 2737 void forceRemoveSelfLocked() { 2738 forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN); 2739 } 2740 2741 @GuardedBy("mLock") 2742 void forceRemoveSelfLocked(int clientState) { 2743 if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi); 2744 2745 final boolean isPendingSaveUi = isSaveUiPendingLocked(); 2746 mPendingSaveUi = null; 2747 removeSelfLocked(); 2748 mUi.destroyAll(mPendingSaveUi, this, false); 2749 if (!isPendingSaveUi) { 2750 try { 2751 mClient.setSessionFinished(clientState); 2752 } catch (RemoteException e) { 2753 Slog.e(TAG, "Error notifying client to finish session", e); 2754 } 2755 } 2756 } 2757 2758 /** 2759 * Thread-safe version of {@link #removeSelfLocked()}. 2760 */ 2761 private void removeSelf() { 2762 synchronized (mLock) { 2763 removeSelfLocked(); 2764 } 2765 } 2766 2767 /** 2768 * Cleans up this session and remove it from the service, but but only if it does not have a 2769 * pending Save UI. 2770 */ 2771 @GuardedBy("mLock") 2772 void removeSelfLocked() { 2773 if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi); 2774 if (mDestroyed) { 2775 Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: " 2776 + id + " destroyed"); 2777 return; 2778 } 2779 if (isSaveUiPendingLocked()) { 2780 Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui"); 2781 return; 2782 } 2783 2784 final RemoteFillService remoteFillService = destroyLocked(); 2785 mService.removeSessionLocked(id); 2786 if (remoteFillService != null) { 2787 remoteFillService.destroy(); 2788 } 2789 } 2790 2791 void onPendingSaveUi(int operation, @NonNull IBinder token) { 2792 getUiForShowing().onPendingSaveUi(operation, token); 2793 } 2794 2795 /** 2796 * Checks whether this session is hiding the Save UI to handle a custom description link for 2797 * a specific {@code token} created by 2798 * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}. 2799 */ 2800 @GuardedBy("mLock") 2801 boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) { 2802 return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken()); 2803 } 2804 2805 /** 2806 * Checks whether this session is hiding the Save UI to handle a custom description link. 2807 */ 2808 @GuardedBy("mLock") 2809 private boolean isSaveUiPendingLocked() { 2810 return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING; 2811 } 2812 2813 @GuardedBy("mLock") 2814 private int getLastResponseIndexLocked() { 2815 // The response ids are monotonically increasing so 2816 // we just find the largest id which is the last. We 2817 // do not rely on the internal ordering in sparse 2818 // array to avoid - wow this stopped working!? 2819 int lastResponseIdx = -1; 2820 int lastResponseId = -1; 2821 if (mResponses != null) { 2822 final int responseCount = mResponses.size(); 2823 for (int i = 0; i < responseCount; i++) { 2824 if (mResponses.keyAt(i) > lastResponseId) { 2825 lastResponseIdx = i; 2826 } 2827 } 2828 } 2829 return lastResponseIdx; 2830 } 2831 2832 private LogMaker newLogMaker(int category) { 2833 return newLogMaker(category, mService.getServicePackageName()); 2834 } 2835 2836 private LogMaker newLogMaker(int category, String servicePackageName) { 2837 return Helper.newLogMaker(category, mComponentName, servicePackageName, id, mCompatMode); 2838 } 2839 2840 private void writeLog(int category) { 2841 mMetricsLogger.write(newLogMaker(category)); 2842 } 2843 2844 private void logAuthenticationStatusLocked(int requestId, int status) { 2845 addTaggedDataToRequestLogLocked(requestId, 2846 MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status); 2847 } 2848 2849 private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) { 2850 final LogMaker requestLog = mRequestLogs.get(requestId); 2851 if (requestLog == null) { 2852 Slog.w(TAG, 2853 "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId); 2854 return; 2855 } 2856 requestLog.addTaggedData(tag, value); 2857 } 2858 2859 private static String requestLogToString(@NonNull LogMaker log) { 2860 final StringWriter sw = new StringWriter(); 2861 final PrintWriter pw = new PrintWriter(sw); 2862 dumpRequestLog(pw, log); 2863 pw.flush(); 2864 return sw.toString(); 2865 } 2866 2867 private void wtf(@Nullable Exception e, String fmt, Object...args) { 2868 final String message = String.format(fmt, args); 2869 mWtfHistory.log(message); 2870 2871 if (e != null) { 2872 Slog.wtf(TAG, message, e); 2873 } else { 2874 Slog.wtf(TAG, message); 2875 } 2876 } 2877 2878 private static String actionAsString(int action) { 2879 switch (action) { 2880 case ACTION_START_SESSION: 2881 return "START_SESSION"; 2882 case ACTION_VIEW_ENTERED: 2883 return "VIEW_ENTERED"; 2884 case ACTION_VIEW_EXITED: 2885 return "VIEW_EXITED"; 2886 case ACTION_VALUE_CHANGED: 2887 return "VALUE_CHANGED"; 2888 default: 2889 return "UNKNOWN_" + action; 2890 } 2891 } 2892 } 2893