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