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