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.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; 20 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; 21 import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE; 22 import static android.service.autofill.Dataset.PICK_REASON_NO_PCC; 23 import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY; 24 import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER; 25 import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY; 26 import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC; 27 import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN; 28 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; 29 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; 30 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; 31 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN; 32 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 33 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; 34 import static android.service.autofill.FillRequest.FLAG_PCC_DETECTION; 35 import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE; 36 import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD; 37 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; 38 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; 39 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; 40 import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED; 41 import static android.view.autofill.AutofillManager.ACTION_START_SESSION; 42 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; 43 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; 44 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; 45 import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED; 46 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; 47 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; 48 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; 49 50 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 51 import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER; 52 import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER; 53 import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE; 54 import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT; 55 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_AUTOFILL_PROVIDER; 56 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_PCC; 57 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN; 58 import static com.android.server.autofill.FillResponseEventLogger.HAVE_SAVE_TRIGGER_ID; 59 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_FAILURE; 60 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SESSION_DESTROYED; 61 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS; 62 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TIMEOUT; 63 import static com.android.server.autofill.Helper.containsCharsInOrder; 64 import static com.android.server.autofill.Helper.createSanitizers; 65 import static com.android.server.autofill.Helper.getNumericValue; 66 import static com.android.server.autofill.Helper.sDebug; 67 import static com.android.server.autofill.Helper.sVerbose; 68 import static com.android.server.autofill.Helper.toArray; 69 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_FAILURE; 70 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS; 71 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION; 72 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION; 73 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS; 74 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED; 75 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT; 76 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY; 77 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_CHANGED; 78 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED; 79 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_DATASET_MATCH; 80 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_FIELD_VALIDATION_FAILED; 81 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_HAS_EMPTY_REQUIRED; 82 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NONE; 83 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NO_SAVE_INFO; 84 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NO_VALUE_CHANGED; 85 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_SESSION_DESTROYED; 86 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG; 87 import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG; 88 import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE; 89 import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE; 90 import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET; 91 import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_UNKNOWN; 92 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS; 93 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; 94 95 import android.annotation.IntDef; 96 import android.annotation.NonNull; 97 import android.annotation.Nullable; 98 import android.app.Activity; 99 import android.app.ActivityTaskManager; 100 import android.app.IAssistDataReceiver; 101 import android.app.PendingIntent; 102 import android.app.assist.AssistStructure; 103 import android.app.assist.AssistStructure.AutofillOverlay; 104 import android.app.assist.AssistStructure.ViewNode; 105 import android.content.BroadcastReceiver; 106 import android.content.ClipData; 107 import android.content.ComponentName; 108 import android.content.Context; 109 import android.content.Intent; 110 import android.content.IntentFilter; 111 import android.content.IntentSender; 112 import android.content.pm.ApplicationInfo; 113 import android.content.pm.PackageManager; 114 import android.content.pm.ServiceInfo; 115 import android.graphics.Bitmap; 116 import android.graphics.Rect; 117 import android.graphics.drawable.Drawable; 118 import android.metrics.LogMaker; 119 import android.os.Binder; 120 import android.os.Build; 121 import android.os.Bundle; 122 import android.os.Handler; 123 import android.os.IBinder; 124 import android.os.IBinder.DeathRecipient; 125 import android.os.Parcelable; 126 import android.os.Process; 127 import android.os.RemoteCallback; 128 import android.os.RemoteException; 129 import android.os.SystemClock; 130 import android.service.assist.classification.FieldClassificationRequest; 131 import android.service.assist.classification.FieldClassificationResponse; 132 import android.service.autofill.AutofillFieldClassificationService.Scores; 133 import android.service.autofill.AutofillService; 134 import android.service.autofill.CompositeUserData; 135 import android.service.autofill.Dataset; 136 import android.service.autofill.Dataset.DatasetEligibleReason; 137 import android.service.autofill.Field; 138 import android.service.autofill.FieldClassification; 139 import android.service.autofill.FieldClassification.Match; 140 import android.service.autofill.FieldClassificationUserData; 141 import android.service.autofill.FillContext; 142 import android.service.autofill.FillEventHistory.Event; 143 import android.service.autofill.FillEventHistory.Event.NoSaveReason; 144 import android.service.autofill.FillRequest; 145 import android.service.autofill.FillResponse; 146 import android.service.autofill.InlinePresentation; 147 import android.service.autofill.InternalSanitizer; 148 import android.service.autofill.InternalValidator; 149 import android.service.autofill.SaveInfo; 150 import android.service.autofill.SaveRequest; 151 import android.service.autofill.UserData; 152 import android.service.autofill.ValueFinder; 153 import android.text.TextUtils; 154 import android.util.ArrayMap; 155 import android.util.ArraySet; 156 import android.util.LocalLog; 157 import android.util.Log; 158 import android.util.Pair; 159 import android.util.Slog; 160 import android.util.SparseArray; 161 import android.util.TimeUtils; 162 import android.view.KeyEvent; 163 import android.view.autofill.AutofillId; 164 import android.view.autofill.AutofillManager; 165 import android.view.autofill.AutofillManager.AutofillCommitReason; 166 import android.view.autofill.AutofillManager.SmartSuggestionMode; 167 import android.view.autofill.AutofillValue; 168 import android.view.autofill.IAutoFillManagerClient; 169 import android.view.autofill.IAutofillWindowPresenter; 170 import android.view.inputmethod.InlineSuggestionsRequest; 171 import android.widget.RemoteViews; 172 173 import com.android.internal.R; 174 import com.android.internal.annotations.GuardedBy; 175 import com.android.internal.logging.MetricsLogger; 176 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 177 import com.android.internal.util.ArrayUtils; 178 import com.android.server.LocalServices; 179 import com.android.server.autofill.ui.AutoFillUI; 180 import com.android.server.autofill.ui.InlineFillUi; 181 import com.android.server.autofill.ui.PendingUi; 182 import com.android.server.inputmethod.InputMethodManagerInternal; 183 import com.android.server.wm.ActivityTaskManagerInternal; 184 185 import java.io.PrintWriter; 186 import java.lang.annotation.Retention; 187 import java.lang.annotation.RetentionPolicy; 188 import java.lang.ref.WeakReference; 189 import java.util.ArrayList; 190 import java.util.Arrays; 191 import java.util.Collection; 192 import java.util.Collections; 193 import java.util.LinkedHashMap; 194 import java.util.LinkedHashSet; 195 import java.util.List; 196 import java.util.Map; 197 import java.util.Objects; 198 import java.util.Optional; 199 import java.util.Set; 200 import java.util.concurrent.atomic.AtomicInteger; 201 import java.util.function.Consumer; 202 import java.util.function.Function; 203 204 /** 205 * A session for a given activity. 206 * 207 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track 208 * of the current {@link ViewState} to display the appropriate UI. 209 * 210 * <p>Although the autofill requests and callbacks are stateless from the service's point of 211 * view, we need to keep state in the framework side for cases such as authentication. For 212 * example, when service return a {@link FillResponse} that contains all the fields needed 213 * to fill the activity but it requires authentication first, that response need to be held 214 * until the user authenticates or it times out. 215 */ 216 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, 217 AutoFillUI.AutoFillUiCallback, ValueFinder, 218 RemoteFieldClassificationService.FieldClassificationServiceCallbacks { 219 private static final String TAG = "AutofillSession"; 220 221 // This should never be true in production. This is only for local debugging. 222 // Otherwise it will spam logcat. 223 private static final boolean DBG = false; 224 225 private static final String ACTION_DELAYED_FILL = 226 "android.service.autofill.action.DELAYED_FILL"; 227 private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; 228 229 private static final String PCC_HINTS_DELIMITER = ","; 230 public static final String EXTRA_KEY_DETECTIONS = "detections"; 231 private static final int DEFAULT__FILL_REQUEST_ID_SNAPSHOT = -2; 232 private static final int DEFAULT__FIELD_CLASSIFICATION_REQUEST_ID_SNAPSHOT = -2; 233 234 final Object mLock; 235 236 private final AutofillManagerServiceImpl mService; 237 private final Handler mHandler; 238 private final AutoFillUI mUi; 239 240 /** 241 * Context associated with the session, it has the same {@link Context#getDisplayId() displayId} 242 * of the activity being autofilled. 243 */ 244 private final Context mContext; 245 246 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 247 248 static final int AUGMENTED_AUTOFILL_REQUEST_ID = 1; 249 250 private static AtomicInteger sIdCounter = new AtomicInteger(2); 251 252 private static AtomicInteger sIdCounterForPcc = new AtomicInteger(2); 253 254 @GuardedBy("mLock") 255 private @SessionState int mSessionState = STATE_UNKNOWN; 256 257 /** Session state uninitiated. */ 258 public static final int STATE_UNKNOWN = 0; 259 260 /** Session is active for filling. */ 261 public static final int STATE_ACTIVE = 1; 262 263 /** Session finished for filling, staying alive for saving. */ 264 public static final int STATE_FINISHED = 2; 265 266 /** Session is destroyed and removed from the manager service. */ 267 public static final int STATE_REMOVED = 3; 268 269 @IntDef(prefix = { "STATE_" }, value = { 270 STATE_UNKNOWN, 271 STATE_ACTIVE, 272 STATE_FINISHED, 273 STATE_REMOVED 274 }) 275 @Retention(RetentionPolicy.SOURCE) 276 @interface SessionState{} 277 278 @GuardedBy("mLock") 279 private final SessionFlags mSessionFlags; 280 281 /** 282 * ID of the session. 283 * 284 * <p>It's always a positive number, to make it easier to embed it in a long. 285 */ 286 public final int id; 287 288 /** userId the session belongs to */ 289 public final int userId; 290 291 /** The uid of the app that's being autofilled */ 292 public final int uid; 293 294 /** ID of the task associated with this session's activity */ 295 public final int taskId; 296 297 /** Flags used to start the session */ 298 public final int mFlags; 299 300 @GuardedBy("mLock") 301 @NonNull private IBinder mActivityToken; 302 303 /** The app activity that's being autofilled */ 304 @NonNull private final ComponentName mComponentName; 305 306 /** Whether the app being autofilled is running in compat mode. */ 307 private final boolean mCompatMode; 308 309 /** Node representing the URL bar on compat mode. */ 310 @GuardedBy("mLock") 311 private ViewNode mUrlBar; 312 313 @GuardedBy("mLock") 314 private boolean mSaveOnAllViewsInvisible; 315 316 @GuardedBy("mLock") 317 private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); 318 319 /** 320 * Tracks the most recent IME inline request and the corresponding request id, for regular 321 * autofill. 322 */ 323 @GuardedBy("mLock") 324 @Nullable private Pair<Integer, InlineSuggestionsRequest> mLastInlineSuggestionsRequest; 325 326 /** 327 * Id of the View currently being displayed. 328 */ 329 @GuardedBy("mLock") 330 private @Nullable AutofillId mCurrentViewId; 331 332 @GuardedBy("mLock") 333 private IAutoFillManagerClient mClient; 334 335 @GuardedBy("mLock") 336 private DeathRecipient mClientVulture; 337 338 @GuardedBy("mLock") 339 private boolean mLoggedInlineDatasetShown; 340 341 /** 342 * Reference to the remote service. 343 * 344 * <p>Only {@code null} when the session is for augmented autofill only. 345 */ 346 @Nullable 347 private final RemoteFillService mRemoteFillService; 348 349 @GuardedBy("mLock") 350 private SparseArray<FillResponse> mResponses; 351 352 /** 353 * Contexts read from the app; they will be updated (sanitized, change values for save) before 354 * sent to {@link AutofillService}. Ordered by the time they were read. 355 */ 356 @GuardedBy("mLock") 357 private ArrayList<FillContext> mContexts; 358 359 /** 360 * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. 361 */ 362 private boolean mHasCallback; 363 364 @GuardedBy("mLock") 365 private boolean mDelayedFillBroadcastReceiverRegistered; 366 367 @GuardedBy("mLock") 368 private PendingIntent mDelayedFillPendingIntent; 369 370 /** 371 * Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is 372 * saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls. 373 */ 374 @GuardedBy("mLock") 375 private Bundle mClientState; 376 377 @GuardedBy("mLock") 378 boolean mDestroyed; 379 380 /** 381 * Helper used to handle state of Save UI when it must be hiding to show a custom description 382 * link and later recovered. 383 */ 384 @GuardedBy("mLock") 385 private PendingUi mPendingSaveUi; 386 387 /** 388 * List of dataset ids selected by the user. 389 */ 390 @GuardedBy("mLock") 391 private ArrayList<String> mSelectedDatasetIds; 392 393 /** 394 * When the session started (using elapsed time since boot). 395 */ 396 private final long mStartTime; 397 398 /** 399 * Count of FillRequests in the session. 400 */ 401 private int mRequestCount; 402 403 /** 404 * Starting timestamp of latency logger. 405 * This is set when Session created or when the view is reset. 406 */ 407 @GuardedBy("mLock") 408 private long mLatencyBaseTime; 409 410 /** 411 * When the UI was shown for the first time (using elapsed time since boot). 412 */ 413 @GuardedBy("mLock") 414 private long mUiShownTime; 415 416 /** 417 * Tracks the value of the fill request id at the time of issuing request for field 418 * classification. 419 */ 420 @GuardedBy("mLock") 421 private int mFillRequestIdSnapshot = DEFAULT__FILL_REQUEST_ID_SNAPSHOT; 422 423 /** 424 * Tracks the value of the field classification id at the time of issuing request for fill 425 * request. 426 */ 427 @GuardedBy("mLock") 428 private int mFieldClassificationIdSnapshot = DEFAULT__FIELD_CLASSIFICATION_REQUEST_ID_SNAPSHOT; 429 430 @GuardedBy("mLock") 431 private final LocalLog mUiLatencyHistory; 432 433 @GuardedBy("mLock") 434 private final LocalLog mWtfHistory; 435 436 /** 437 * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. 438 */ 439 @GuardedBy("mLock") 440 private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1); 441 442 /** 443 * Destroys the augmented Autofill UI. 444 */ 445 // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the 446 // main reason being the cases where user tap HOME. 447 // Right now it's completely destroying the UI, but we need to decide whether / how to 448 // properly recover it later (for example, if the user switches back to the activity, 449 // should it be restored? Right now it kind of is, because Autofill's Session trigger a 450 // new FillRequest, which in turn triggers the Augmented Autofill request again) 451 @GuardedBy("mLock") 452 @Nullable 453 private Runnable mAugmentedAutofillDestroyer; 454 455 /** 456 * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. 457 */ 458 @GuardedBy("mLock") 459 private ArrayList<LogMaker> mAugmentedRequestsLogs; 460 461 462 /** 463 * List of autofill ids of autofillable fields present in the AssistStructure that can be used 464 * to trigger new augmented autofill requests (because the "standard" service was not interested 465 * on autofilling the app. 466 */ 467 @GuardedBy("mLock") 468 private ArrayList<AutofillId> mAugmentedAutofillableIds; 469 470 @NonNull 471 final AutofillInlineSessionController mInlineSessionController; 472 473 /** 474 * Receiver of assist data from the app's {@link Activity}. 475 */ 476 private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl(); 477 478 /** 479 * Receiver of assist data for pcc purpose 480 */ 481 private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl(); 482 483 private final ClassificationState mClassificationState = new ClassificationState(); 484 485 // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a 486 // new one per Session. 487 private final BroadcastReceiver mDelayedFillBroadcastReceiver = 488 new BroadcastReceiver() { 489 // ErrorProne says mAssistReceiver#processDelayedFillLocked needs to be guarded by 490 // 'Session.this.mLock', which is the same as mLock. 491 @SuppressWarnings("GuardedBy") 492 @Override 493 public void onReceive(final Context context, final Intent intent) { 494 if (!intent.getAction().equals(ACTION_DELAYED_FILL)) { 495 Slog.wtf(TAG, "Unexpected action is received."); 496 return; 497 } 498 if (!intent.hasExtra(EXTRA_REQUEST_ID)) { 499 Slog.e(TAG, "Delay fill action is missing request id extra."); 500 return; 501 } 502 Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received"); 503 synchronized (mLock) { 504 int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0); 505 FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class); 506 mAssistReceiver.processDelayedFillLocked(requestId, response); 507 } 508 } 509 }; 510 511 @NonNull 512 @GuardedBy("mLock") 513 private PresentationStatsEventLogger mPresentationStatsEventLogger; 514 515 @NonNull 516 @GuardedBy("mLock") 517 private FillRequestEventLogger mFillRequestEventLogger; 518 519 @NonNull 520 @GuardedBy("mLock") 521 private FillResponseEventLogger mFillResponseEventLogger; 522 523 @NonNull 524 @GuardedBy("mLock") 525 private SaveEventLogger mSaveEventLogger; 526 527 @NonNull 528 @GuardedBy("mLock") 529 private SessionCommittedEventLogger mSessionCommittedEventLogger; 530 531 /** 532 * Fill dialog request would likely be sent slightly later. 533 */ 534 @NonNull 535 @GuardedBy("mLock") 536 private boolean mPreviouslyFillDialogPotentiallyStarted; 537 538 /** 539 * Keeps track of if the user entered view, this is used to 540 * distinguish Fill Request that did not have user interaction 541 * with ones that did. 542 * 543 * This is set to true when entering view - after FillDialog FillRequest 544 * or on plain user tap. 545 */ 546 @NonNull 547 @GuardedBy("mLock") 548 private boolean mLogViewEntered; 549 550 /** 551 * Keeps the fill dialog trigger ids of the last response. This invalidates 552 * the trigger ids of the previous response. 553 */ 554 @Nullable 555 @GuardedBy("mLock") 556 private AutofillId[] mLastFillDialogTriggerIds; 557 onSwitchInputMethodLocked()558 void onSwitchInputMethodLocked() { 559 // One caveat is that for the case where the focus is on a field for which regular autofill 560 // returns null, and augmented autofill is triggered, and then the user switches the input 561 // method. Tapping on the field again will not trigger a new augmented autofill request. 562 // This may be fixed by adding more checks such as whether mCurrentViewId is null. 563 if (mSessionFlags.mExpiredResponse) { 564 return; 565 } 566 if (shouldResetSessionStateOnInputMethodSwitch()) { 567 // Set the old response expired, so the next action (ACTION_VIEW_ENTERED) can trigger 568 // a new fill request. 569 mSessionFlags.mExpiredResponse = true; 570 // Clear the augmented autofillable ids so augmented autofill will trigger again. 571 mAugmentedAutofillableIds = null; 572 // In case the field is augmented autofill only, we clear the current view id, so that 573 // we won't skip view entered due to same view entered, for the augmented autofill. 574 if (mSessionFlags.mAugmentedAutofillOnly) { 575 mCurrentViewId = null; 576 } 577 } 578 } 579 shouldResetSessionStateOnInputMethodSwitch()580 private boolean shouldResetSessionStateOnInputMethodSwitch() { 581 // One of below cases will need a new fill request to update the inline spec for the new 582 // input method. 583 // 1. The autofill provider supports inline suggestion and the render service is available. 584 // 2. Had triggered the augmented autofill and the render service is available. Whether the 585 // augmented autofill triggered by: 586 // a. Augmented autofill only 587 // b. The autofill provider respond null 588 if (mService.getRemoteInlineSuggestionRenderServiceLocked() == null) { 589 return false; 590 } 591 592 if (mSessionFlags.mInlineSupportedByService) { 593 return true; 594 } 595 596 final ViewState state = mViewStates.get(mCurrentViewId); 597 if (state != null 598 && (state.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { 599 return true; 600 } 601 602 return false; 603 } 604 605 /** 606 * Collection of flags/booleans that helps determine Session behaviors. 607 */ 608 private final class SessionFlags { 609 /** Whether autofill is disabled by the service */ 610 private boolean mAutofillDisabled; 611 612 /** Whether the autofill service supports inline suggestions */ 613 private boolean mInlineSupportedByService; 614 615 /** True if session is for augmented only */ 616 private boolean mAugmentedAutofillOnly; 617 618 /** Whether the session is currently showing the SaveUi. */ 619 private boolean mShowingSaveUi; 620 621 /** Whether the current {@link FillResponse} is expired. */ 622 private boolean mExpiredResponse; 623 624 /** Whether the fill dialog UI is disabled. */ 625 private boolean mFillDialogDisabled; 626 627 /** Whether current screen has credman field. */ 628 private boolean mScreenHasCredmanField; 629 } 630 631 /** 632 * TODO(b/151867668): improve how asynchronous data dependencies are handled, without using 633 * CountDownLatch. 634 */ 635 final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { 636 @GuardedBy("mLock") 637 private boolean mWaitForInlineRequest; 638 @GuardedBy("mLock") 639 private InlineSuggestionsRequest mPendingInlineSuggestionsRequest; 640 @GuardedBy("mLock") 641 private FillRequest mPendingFillRequest; 642 @GuardedBy("mLock") 643 private FillRequest mLastFillRequest; 644 newAutofillRequestLocked(ViewState viewState, boolean isInlineRequest)645 @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState, 646 boolean isInlineRequest) { 647 mPendingFillRequest = null; 648 mWaitForInlineRequest = isInlineRequest; 649 mPendingInlineSuggestionsRequest = null; 650 if (isInlineRequest) { 651 WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference = 652 new WeakReference<AssistDataReceiverImpl>(this); 653 WeakReference<ViewState> viewStateWeakReference = 654 new WeakReference<ViewState>(viewState); 655 return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference, 656 viewStateWeakReference); 657 } 658 return null; 659 } 660 handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, ViewState viewState)661 void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, 662 ViewState viewState) { 663 synchronized (mLock) { 664 if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { 665 return; 666 } 667 mWaitForInlineRequest = inlineSuggestionsRequest != null; 668 mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; 669 maybeRequestFillLocked(); 670 viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); 671 } 672 } 673 674 @GuardedBy("mLock") maybeRequestFillLocked()675 void maybeRequestFillLocked() { 676 if (mPendingFillRequest == null) { 677 return; 678 } 679 mFieldClassificationIdSnapshot = sIdCounterForPcc.get(); 680 681 if (mWaitForInlineRequest) { 682 if (mPendingInlineSuggestionsRequest == null) { 683 return; 684 } 685 686 mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), 687 mPendingFillRequest.getFillContexts(), 688 mPendingFillRequest.getHints(), 689 mPendingFillRequest.getClientState(), 690 mPendingFillRequest.getFlags(), 691 mPendingInlineSuggestionsRequest, 692 mPendingFillRequest.getDelayedFillIntentSender()); 693 } 694 mLastFillRequest = mPendingFillRequest; 695 696 mRemoteFillService.onFillRequest(mPendingFillRequest); 697 mPendingInlineSuggestionsRequest = null; 698 mWaitForInlineRequest = false; 699 mPendingFillRequest = null; 700 701 final long fillRequestSentRelativeTimestamp = 702 SystemClock.elapsedRealtime() - mLatencyBaseTime; 703 mPresentationStatsEventLogger.maybeSetFillRequestSentTimestampMs( 704 (int) (fillRequestSentRelativeTimestamp)); 705 mFillRequestEventLogger.maybeSetLatencyFillRequestSentMillis( 706 (int) (fillRequestSentRelativeTimestamp)); 707 mFillRequestEventLogger.logAndEndEvent(); 708 } 709 710 @Override onHandleAssistData(Bundle resultData)711 public void onHandleAssistData(Bundle resultData) throws RemoteException { 712 if (mRemoteFillService == null) { 713 wtf(null, "onHandleAssistData() called without a remote service. " 714 + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); 715 return; 716 } 717 // Keeps to prevent it is cleared on multiple threads. 718 final AutofillId currentViewId = mCurrentViewId; 719 if (currentViewId == null) { 720 Slog.w(TAG, "No current view id - session might have finished"); 721 return; 722 } 723 724 final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); 725 if (structure == null) { 726 Slog.e(TAG, "No assist structure - app might have crashed providing it"); 727 return; 728 } 729 730 final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS); 731 if (receiverExtras == null) { 732 Slog.e(TAG, "No receiver extras - app might have crashed providing it"); 733 return; 734 } 735 736 final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); 737 738 if (sVerbose) { 739 Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure); 740 } 741 742 final FillRequest request; 743 synchronized (mLock) { 744 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(), 745 // even if if the activity is gone by then, but structure .ensureData() gives a 746 // ONE_WAY warning because system_service could block on app calls. We need to 747 // change AssistStructure so it provides a "one-way" writeToParcel() method that 748 // sends all the data 749 try { 750 structure.ensureDataForAutofill(); 751 } catch (RuntimeException e) { 752 wtf(e, "Exception lazy loading assist structure for %s: %s", 753 structure.getActivityComponent(), e); 754 return; 755 } 756 757 final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure, 758 /* autofillableOnly= */false); 759 for (int i = 0; i < ids.size(); i++) { 760 ids.get(i).setSessionId(Session.this.id); 761 } 762 763 // Flags used to start the session. 764 int flags = structure.getFlags(); 765 766 if (mCompatMode) { 767 // Sanitize URL bar, if needed 768 final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode( 769 mComponentName.getPackageName()); 770 if (sDebug) { 771 Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds)); 772 } 773 if (urlBarIds != null) { 774 mUrlBar = Helper.sanitizeUrlBar(structure, urlBarIds); 775 if (mUrlBar != null) { 776 final AutofillId urlBarId = mUrlBar.getAutofillId(); 777 if (sDebug) { 778 Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain " 779 + mUrlBar.getWebDomain()); 780 } 781 final ViewState viewState = new ViewState(urlBarId, Session.this, 782 ViewState.STATE_URL_BAR); 783 mViewStates.put(urlBarId, viewState); 784 } 785 } 786 flags |= FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST; 787 } 788 structure.sanitizeForParceling(true); 789 790 if (mContexts == null) { 791 mContexts = new ArrayList<>(1); 792 } 793 mContexts.add(new FillContext(requestId, structure, currentViewId)); 794 795 cancelCurrentRequestLocked(); 796 797 final int numContexts = mContexts.size(); 798 for (int i = 0; i < numContexts; i++) { 799 fillContextWithAllowedValuesLocked(mContexts.get(i), flags); 800 } 801 802 final ArrayList<FillContext> contexts = 803 mergePreviousSessionLocked(/* forSave= */ false); 804 final List<String> hints = getTypeHintsForProvider(); 805 806 mDelayedFillPendingIntent = createPendingIntent(requestId); 807 request = new FillRequest(requestId, contexts, hints, mClientState, flags, 808 /*inlineSuggestionsRequest=*/ null, 809 /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null 810 ? null 811 : mDelayedFillPendingIntent.getIntentSender()); 812 813 mPendingFillRequest = request; 814 maybeRequestFillLocked(); 815 } 816 817 if (mActivityToken != null) { 818 mService.sendActivityAssistDataToContentCapture(mActivityToken, resultData); 819 } 820 } 821 822 @Override onHandleAssistScreenshot(Bitmap screenshot)823 public void onHandleAssistScreenshot(Bitmap screenshot) { 824 // Do nothing 825 } 826 827 @GuardedBy("mLock") processDelayedFillLocked(int requestId, FillResponse response)828 void processDelayedFillLocked(int requestId, FillResponse response) { 829 if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) { 830 Slog.v(TAG, "processDelayedFillLocked: " 831 + "calling onFillRequestSuccess with new response"); 832 onFillRequestSuccess(requestId, response, 833 mService.getServicePackageName(), mLastFillRequest.getFlags()); 834 } 835 } 836 } 837 838 /** 839 * Get the list of valid autofill hint types from Device flags 840 * Returns empty list if PCC is off or no types available 841 */ getTypeHintsForProvider()842 private List<String> getTypeHintsForProvider() { 843 if (!mService.isPccClassificationEnabled()) { 844 return Collections.EMPTY_LIST; 845 } 846 final String typeHints = mService.getMaster().getPccProviderHints(); 847 if (sVerbose) { 848 Slog.v(TAG, "TypeHints flag:" + typeHints); 849 } 850 if (TextUtils.isEmpty(typeHints)) { 851 return new ArrayList<>(); 852 } 853 854 return List.of(typeHints.split(PCC_HINTS_DELIMITER)); 855 } 856 857 /** 858 * Assist Data Receiver for PCC 859 */ 860 private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub { 861 862 @GuardedBy("mLock") maybeRequestFieldClassificationFromServiceLocked()863 void maybeRequestFieldClassificationFromServiceLocked() { 864 if (mClassificationState.mPendingFieldClassificationRequest == null) { 865 Slog.w(TAG, "Received AssistData without pending classification request"); 866 return; 867 } 868 869 RemoteFieldClassificationService remoteFieldClassificationService = 870 mService.getRemoteFieldClassificationServiceLocked(); 871 if (remoteFieldClassificationService != null) { 872 WeakReference<RemoteFieldClassificationService.FieldClassificationServiceCallbacks> 873 fieldClassificationServiceCallbacksWeakRef = 874 new WeakReference<>(Session.this); 875 remoteFieldClassificationService.onFieldClassificationRequest( 876 mClassificationState.mPendingFieldClassificationRequest, 877 fieldClassificationServiceCallbacksWeakRef); 878 } 879 mClassificationState.onFieldClassificationRequestSent(); 880 } 881 882 @Override onHandleAssistData(Bundle resultData)883 public void onHandleAssistData(Bundle resultData) throws RemoteException { 884 // TODO: add a check if pcc field classification service is present 885 final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, 886 android.app.assist.AssistStructure.class); 887 if (structure == null) { 888 Slog.e(TAG, "No assist structure for pcc detection - " 889 + "app might have crashed providing it"); 890 return; 891 } 892 893 final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS); 894 if (receiverExtras == null) { 895 Slog.e(TAG, "No receiver extras for pcc detection - " 896 + "app might have crashed providing it"); 897 return; 898 } 899 900 final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); 901 902 if (sVerbose) { 903 Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": " 904 + structure); 905 } 906 907 synchronized (mLock) { 908 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(), 909 // even if the activity is gone by then, but structure .ensureData() gives a 910 // ONE_WAY warning because system_service could block on app calls. We need to 911 // change AssistStructure so it provides a "one-way" writeToParcel() method that 912 // sends all the data 913 try { 914 structure.ensureDataForAutofill(); 915 } catch (RuntimeException e) { 916 wtf(e, "Exception lazy loading assist structure for %s: %s", 917 structure.getActivityComponent(), e); 918 return; 919 } 920 921 final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure, 922 /* autofillableOnly= */false); 923 for (int i = 0; i < ids.size(); i++) { 924 ids.get(i).setSessionId(Session.this.id); 925 } 926 927 mClassificationState.onAssistStructureReceived(structure); 928 929 maybeRequestFieldClassificationFromServiceLocked(); 930 } 931 } 932 933 @Override onHandleAssistScreenshot(Bitmap screenshot)934 public void onHandleAssistScreenshot(Bitmap screenshot) { 935 // Do nothing 936 } 937 } 938 939 /** Creates {@link PendingIntent} for autofill service to send a delayed fill. */ createPendingIntent(int requestId)940 private PendingIntent createPendingIntent(int requestId) { 941 Slog.d(TAG, "createPendingIntent for request " + requestId); 942 PendingIntent pendingIntent; 943 final long identity = Binder.clearCallingIdentity(); 944 try { 945 Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android") 946 .putExtra(EXTRA_REQUEST_ID, requestId); 947 pendingIntent = PendingIntent.getBroadcast( 948 mContext, this.id, intent, 949 PendingIntent.FLAG_MUTABLE 950 | PendingIntent.FLAG_ONE_SHOT 951 | PendingIntent.FLAG_CANCEL_CURRENT); 952 } finally { 953 Binder.restoreCallingIdentity(identity); 954 } 955 return pendingIntent; 956 } 957 958 @GuardedBy("mLock") clearPendingIntentLocked()959 private void clearPendingIntentLocked() { 960 Slog.d(TAG, "clearPendingIntentLocked"); 961 if (mDelayedFillPendingIntent == null) { 962 return; 963 } 964 final long identity = Binder.clearCallingIdentity(); 965 try { 966 mDelayedFillPendingIntent.cancel(); 967 mDelayedFillPendingIntent = null; 968 } finally { 969 Binder.restoreCallingIdentity(identity); 970 } 971 } 972 973 @GuardedBy("mLock") registerDelayedFillBroadcastLocked()974 private void registerDelayedFillBroadcastLocked() { 975 if (!mDelayedFillBroadcastReceiverRegistered) { 976 Slog.v(TAG, "registerDelayedFillBroadcastLocked()"); 977 IntentFilter intentFilter = new IntentFilter(ACTION_DELAYED_FILL); 978 mContext.registerReceiver(mDelayedFillBroadcastReceiver, intentFilter); 979 mDelayedFillBroadcastReceiverRegistered = true; 980 } 981 } 982 983 @GuardedBy("mLock") unregisterDelayedFillBroadcastLocked()984 private void unregisterDelayedFillBroadcastLocked() { 985 if (mDelayedFillBroadcastReceiverRegistered) { 986 Slog.v(TAG, "unregisterDelayedFillBroadcastLocked()"); 987 mContext.unregisterReceiver(mDelayedFillBroadcastReceiver); 988 mDelayedFillBroadcastReceiverRegistered = false; 989 } 990 } 991 992 /** 993 * Returns the ids of all entries in {@link #mViewStates} in the same order. 994 */ 995 @GuardedBy("mLock") getIdsOfAllViewStatesLocked()996 private AutofillId[] getIdsOfAllViewStatesLocked() { 997 final int numViewState = mViewStates.size(); 998 final AutofillId[] ids = new AutofillId[numViewState]; 999 for (int i = 0; i < numViewState; i++) { 1000 ids[i] = mViewStates.valueAt(i).id; 1001 } 1002 1003 return ids; 1004 } 1005 1006 /** 1007 * Returns the String value of an {@link AutofillValue} by {@link AutofillId id} if it is of 1008 * type {@code AUTOFILL_TYPE_TEXT} or {@code AUTOFILL_TYPE_LIST}. 1009 */ 1010 @Override 1011 @Nullable findByAutofillId(@onNull AutofillId id)1012 public String findByAutofillId(@NonNull AutofillId id) { 1013 synchronized (mLock) { 1014 AutofillValue value = findValueLocked(id); 1015 if (value != null) { 1016 if (value.isText()) { 1017 return value.getTextValue().toString(); 1018 } 1019 1020 if (value.isList()) { 1021 final CharSequence[] options = getAutofillOptionsFromContextsLocked(id); 1022 if (options != null) { 1023 final int index = value.getListValue(); 1024 final CharSequence option = options[index]; 1025 return option != null ? option.toString() : null; 1026 } else { 1027 Slog.w(TAG, "findByAutofillId(): no autofill options for id " + id); 1028 } 1029 } 1030 } 1031 } 1032 return null; 1033 } 1034 1035 @Override findRawValueByAutofillId(AutofillId id)1036 public AutofillValue findRawValueByAutofillId(AutofillId id) { 1037 synchronized (mLock) { 1038 return findValueLocked(id); 1039 } 1040 } 1041 1042 /** 1043 * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, 1044 * or {@code null} when not found on either of them. 1045 */ 1046 @GuardedBy("mLock") 1047 @Nullable findValueLocked(@onNull AutofillId autofillId)1048 private AutofillValue findValueLocked(@NonNull AutofillId autofillId) { 1049 final AutofillValue value = findValueFromThisSessionOnlyLocked(autofillId); 1050 if (value != null) { 1051 return getSanitizedValue(createSanitizers(getSaveInfoLocked()), autofillId, value); 1052 } 1053 1054 // TODO(b/113281366): rather than explicitly look for previous session, it might be better 1055 // to merge the sessions when created (see note on mergePreviousSessionLocked()) 1056 final ArrayList<Session> previousSessions = mService.getPreviousSessionsLocked(this); 1057 if (previousSessions != null) { 1058 if (sDebug) { 1059 Slog.d(TAG, "findValueLocked(): looking on " + previousSessions.size() 1060 + " previous sessions for autofillId " + autofillId); 1061 } 1062 for (int i = 0; i < previousSessions.size(); i++) { 1063 final Session previousSession = previousSessions.get(i); 1064 final AutofillValue previousValue = previousSession 1065 .findValueFromThisSessionOnlyLocked(autofillId); 1066 if (previousValue != null) { 1067 return getSanitizedValue(createSanitizers(previousSession.getSaveInfoLocked()), 1068 autofillId, previousValue); 1069 } 1070 } 1071 } 1072 return null; 1073 } 1074 1075 @Nullable findValueFromThisSessionOnlyLocked(@onNull AutofillId autofillId)1076 private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) { 1077 final ViewState state = mViewStates.get(autofillId); 1078 if (state == null) { 1079 if (sDebug) Slog.d(TAG, "findValueLocked(): no view state for " + autofillId); 1080 return null; 1081 } 1082 AutofillValue value = state.getCurrentValue(); 1083 if (value == null) { 1084 if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId); 1085 value = getValueFromContextsLocked(autofillId); 1086 } 1087 return value; 1088 } 1089 1090 /** 1091 * Updates values of the nodes in the context's structure so that: 1092 * 1093 * - proper node is focused 1094 * - autofillValue is sent back to service when it was previously autofilled 1095 * - autofillValue is sent in the view used to force a request 1096 * 1097 * @param fillContext The context to be filled 1098 * @param flags The flags that started the session 1099 */ 1100 @GuardedBy("mLock") fillContextWithAllowedValuesLocked(@onNull FillContext fillContext, int flags)1101 private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) { 1102 final ViewNode[] nodes = fillContext 1103 .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); 1104 1105 final int numViewState = mViewStates.size(); 1106 for (int i = 0; i < numViewState; i++) { 1107 final ViewState viewState = mViewStates.valueAt(i); 1108 1109 final ViewNode node = nodes[i]; 1110 if (node == null) { 1111 if (sVerbose) { 1112 Slog.v(TAG, 1113 "fillContextWithAllowedValuesLocked(): no node for " + viewState.id); 1114 } 1115 continue; 1116 } 1117 1118 final AutofillValue currentValue = viewState.getCurrentValue(); 1119 final AutofillValue filledValue = viewState.getAutofilledValue(); 1120 final AutofillOverlay overlay = new AutofillOverlay(); 1121 1122 // Sanitizes the value if the current value matches what the service sent. 1123 if (filledValue != null && filledValue.equals(currentValue)) { 1124 overlay.value = currentValue; 1125 } 1126 1127 if (mCurrentViewId != null) { 1128 // Updates the focus value. 1129 overlay.focused = mCurrentViewId.equals(viewState.id); 1130 // Sanitizes the value of the focused field in a manual request. 1131 if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) { 1132 overlay.value = currentValue; 1133 } 1134 } 1135 node.setAutofillOverlay(overlay); 1136 } 1137 } 1138 1139 /** 1140 * Cancels the last request sent to the {@link #mRemoteFillService}. 1141 */ 1142 @GuardedBy("mLock") cancelCurrentRequestLocked()1143 private void cancelCurrentRequestLocked() { 1144 if (mRemoteFillService == null) { 1145 wtf(null, "cancelCurrentRequestLocked() called without a remote service. " 1146 + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); 1147 return; 1148 } 1149 final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); 1150 1151 // Remove the FillContext as there will never be a response for the service 1152 if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { 1153 final int numContexts = mContexts.size(); 1154 1155 // It is most likely the last context, hence search backwards 1156 for (int i = numContexts - 1; i >= 0; i--) { 1157 if (mContexts.get(i).getRequestId() == canceledRequest) { 1158 if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); 1159 mContexts.remove(i); 1160 break; 1161 } 1162 } 1163 } 1164 } 1165 isViewFocusedLocked(int flags)1166 private boolean isViewFocusedLocked(int flags) { 1167 return (flags & FLAG_VIEW_NOT_FOCUSED) == 0; 1168 } 1169 1170 /** 1171 * Clears the existing response for the partition, reads a new structure, and then requests a 1172 * new fill response from the fill service. 1173 * 1174 * <p> Also asks the IME to make an inline suggestions request if it's enabled. 1175 */ 1176 @GuardedBy("mLock") requestNewFillResponseLocked(@onNull ViewState viewState, int newState, int flags)1177 private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, 1178 int flags) { 1179 final FillResponse existingResponse = viewState.getResponse(); 1180 mFillRequestEventLogger.startLogForNewRequest(); 1181 mRequestCount++; 1182 mFillRequestEventLogger.maybeSetAppPackageUid(uid); 1183 mFillRequestEventLogger.maybeSetFlags(mFlags); 1184 if(mPreviouslyFillDialogPotentiallyStarted) { 1185 mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER); 1186 } else { 1187 mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER); 1188 } 1189 if (existingResponse != null) { 1190 setViewStatesLocked( 1191 existingResponse, 1192 ViewState.STATE_INITIAL, 1193 /* clearResponse= */ true); 1194 mFillRequestEventLogger.maybeSetRequestTriggerReason( 1195 TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE); 1196 } 1197 mSessionFlags.mExpiredResponse = false; 1198 mSessionState = STATE_ACTIVE; 1199 if (mSessionFlags.mAugmentedAutofillOnly || mRemoteFillService == null) { 1200 if (sVerbose) { 1201 Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead " 1202 + "(mForAugmentedAutofillOnly=" + mSessionFlags.mAugmentedAutofillOnly 1203 + ", flags=" + flags + ")"); 1204 } 1205 mSessionFlags.mAugmentedAutofillOnly = true; 1206 mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID); 1207 mFillRequestEventLogger.maybeSetIsAugmented(true); 1208 mFillRequestEventLogger.logAndEndEvent(); 1209 triggerAugmentedAutofillLocked(flags); 1210 return; 1211 } 1212 1213 viewState.setState(newState); 1214 1215 int requestId; 1216 // TODO(b/158623971): Update this to prevent possible overflow 1217 do { 1218 requestId = sIdCounter.getAndIncrement(); 1219 } while (requestId == INVALID_REQUEST_ID); 1220 1221 // Create a metrics log for the request 1222 final int ordinal = mRequestLogs.size() + 1; 1223 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST) 1224 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal); 1225 if (flags != 0) { 1226 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags); 1227 } 1228 mRequestLogs.put(requestId, log); 1229 1230 if (sVerbose) { 1231 Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId 1232 + ", flags=" + flags); 1233 } 1234 mPresentationStatsEventLogger.maybeSetRequestId(requestId); 1235 mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( 1236 mFieldClassificationIdSnapshot); 1237 mFillRequestEventLogger.maybeSetRequestId(requestId); 1238 mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); 1239 if (mSessionFlags.mInlineSupportedByService) { 1240 mFillRequestEventLogger.maybeSetInlineSuggestionHostUid(mContext, userId); 1241 } 1242 mFillRequestEventLogger.maybeSetIsFillDialogEligible(!mSessionFlags.mFillDialogDisabled); 1243 1244 // If the focus changes very quickly before the first request is returned each focus change 1245 // triggers a new partition and we end up with many duplicate partitions. This is 1246 // enhanced as the focus change can be much faster than the taking of the assist structure. 1247 // Hence remove the currently queued request and replace it with the one queued after the 1248 // structure is taken. This causes only one fill request per burst of focus changes. 1249 cancelCurrentRequestLocked(); 1250 1251 if (mService.isPccClassificationEnabled() 1252 && mClassificationState.mHintsToAutofillIdMap == null) { 1253 if (sVerbose) { 1254 Slog.v(TAG, "triggering field classification"); 1255 } 1256 requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION); 1257 } 1258 1259 // Only ask IME to create inline suggestions request if Autofill provider supports it and 1260 // the render service is available except the autofill is triggered manually and the view 1261 // is also not focused. 1262 final RemoteInlineSuggestionRenderService remoteRenderService = 1263 mService.getRemoteInlineSuggestionRenderServiceLocked(); 1264 if (mSessionFlags.mInlineSupportedByService 1265 && remoteRenderService != null 1266 && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { 1267 1268 Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = 1269 mAssistReceiver.newAutofillRequestLocked(viewState, 1270 /* isInlineRequest= */ true); 1271 1272 if (inlineSuggestionsRequestConsumer != null) { 1273 final int requestIdCopy = requestId; 1274 final AutofillId focusedId = mCurrentViewId; 1275 1276 WeakReference sessionWeakReference = new WeakReference<Session>(this); 1277 InlineSuggestionRendorInfoCallbackOnResultListener 1278 inlineSuggestionRendorInfoCallbackOnResultListener = 1279 new InlineSuggestionRendorInfoCallbackOnResultListener( 1280 sessionWeakReference, 1281 requestIdCopy, 1282 inlineSuggestionsRequestConsumer, 1283 focusedId); 1284 RemoteCallback inlineSuggestionRendorInfoCallback = new RemoteCallback( 1285 inlineSuggestionRendorInfoCallbackOnResultListener, mHandler); 1286 1287 remoteRenderService.getInlineSuggestionsRendererInfo( 1288 inlineSuggestionRendorInfoCallback); 1289 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); 1290 } 1291 } else { 1292 mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false); 1293 } 1294 1295 // Now request the assist structure data. 1296 requestAssistStructureLocked(requestId, flags); 1297 } 1298 isRequestSupportFillDialog(int flags)1299 private boolean isRequestSupportFillDialog(int flags) { 1300 return (flags & FLAG_SUPPORTS_FILL_DIALOG) != 0; 1301 } 1302 1303 @GuardedBy("mLock") requestAssistStructureForPccLocked(int flags)1304 private void requestAssistStructureForPccLocked(int flags) { 1305 if (!mClassificationState.shouldTriggerRequest()) return; 1306 mFillRequestIdSnapshot = sIdCounter.get(); 1307 mClassificationState.updatePendingRequest(); 1308 // Get request id 1309 int requestId; 1310 // TODO(b/158623971): Update this to prevent possible overflow 1311 do { 1312 requestId = sIdCounterForPcc.getAndIncrement(); 1313 } while (requestId == INVALID_REQUEST_ID); 1314 1315 if (sVerbose) { 1316 Slog.v(TAG, "request id is " + requestId + ", requesting assist structure for pcc"); 1317 } 1318 // Call requestAutofilLData 1319 try { 1320 final Bundle receiverExtras = new Bundle(); 1321 receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); 1322 final long identity = Binder.clearCallingIdentity(); 1323 try { 1324 if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver, 1325 receiverExtras, mActivityToken, flags)) { 1326 Slog.w(TAG, "failed to request autofill data for " + mActivityToken); 1327 } 1328 } finally { 1329 Binder.restoreCallingIdentity(identity); 1330 } 1331 } catch (RemoteException e) { 1332 } 1333 } 1334 1335 @GuardedBy("mLock") requestAssistStructureLocked(int requestId, int flags)1336 private void requestAssistStructureLocked(int requestId, int flags) { 1337 try { 1338 final Bundle receiverExtras = new Bundle(); 1339 receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); 1340 final long identity = Binder.clearCallingIdentity(); 1341 try { 1342 if (!ActivityTaskManager.getService().requestAutofillData(mAssistReceiver, 1343 receiverExtras, mActivityToken, flags)) { 1344 Slog.w(TAG, "failed to request autofill data for " + mActivityToken); 1345 } 1346 } finally { 1347 Binder.restoreCallingIdentity(identity); 1348 } 1349 } catch (RemoteException e) { 1350 // Should not happen, it's a local call. 1351 } 1352 } 1353 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, @NonNull InputMethodManagerInternal inputMethodManagerInternal)1354 Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, 1355 @NonNull Context context, @NonNull Handler handler, int userId, @NonNull Object lock, 1356 int sessionId, int taskId, int uid, @NonNull IBinder activityToken, 1357 @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, 1358 @NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName, 1359 @NonNull ComponentName componentName, boolean compatMode, 1360 boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags, 1361 @NonNull InputMethodManagerInternal inputMethodManagerInternal) { 1362 if (sessionId < 0) { 1363 wtf(null, "Non-positive sessionId: %s", sessionId); 1364 } 1365 id = sessionId; 1366 mFlags = flags; 1367 this.userId = userId; 1368 this.taskId = taskId; 1369 this.uid = uid; 1370 mService = service; 1371 mLock = lock; 1372 mUi = ui; 1373 mHandler = handler; 1374 mRemoteFillService = serviceComponentName == null ? null 1375 : new RemoteFillService(context, serviceComponentName, userId, this, 1376 bindInstantServiceAllowed); 1377 mActivityToken = activityToken; 1378 mHasCallback = hasCallback; 1379 mUiLatencyHistory = uiLatencyHistory; 1380 mWtfHistory = wtfHistory; 1381 int displayId = LocalServices.getService(ActivityTaskManagerInternal.class) 1382 .getDisplayId(activityToken); 1383 mContext = Helper.getDisplayContext(context, displayId); 1384 mComponentName = componentName; 1385 mCompatMode = compatMode; 1386 mSessionState = STATE_ACTIVE; 1387 // Initiate all loggers & counters. 1388 mStartTime = SystemClock.elapsedRealtime(); 1389 mLatencyBaseTime = mStartTime; 1390 mRequestCount = 0; 1391 mPresentationStatsEventLogger = PresentationStatsEventLogger.createPresentationLog( 1392 sessionId, uid); 1393 mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId); 1394 mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId); 1395 mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId); 1396 mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid); 1397 mSaveEventLogger = SaveEventLogger.forSessionId(sessionId); 1398 1399 synchronized (mLock) { 1400 mSessionFlags = new SessionFlags(); 1401 mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly; 1402 mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked(); 1403 setClientLocked(client); 1404 } 1405 1406 mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal, 1407 userId, componentName, handler, mLock, 1408 new InlineFillUi.InlineUiEventCallback() { 1409 @Override 1410 public void notifyInlineUiShown(AutofillId autofillId) { 1411 notifyFillUiShown(autofillId); 1412 1413 synchronized (mLock) { 1414 // TODO(b/262448552): Log when chip inflates instead of here 1415 final long inlineUiShownRelativeTimestamp = 1416 SystemClock.elapsedRealtime() - mLatencyBaseTime; 1417 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs( 1418 (int) (inlineUiShownRelativeTimestamp)); 1419 } 1420 } 1421 1422 @Override 1423 public void notifyInlineUiHidden(AutofillId autofillId) { 1424 notifyFillUiHidden(autofillId); 1425 } 1426 }); 1427 1428 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) 1429 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); 1430 mLogViewEntered = false; 1431 } 1432 1433 /** 1434 * Gets the currently registered activity token 1435 * 1436 * @return The activity token 1437 */ 1438 @GuardedBy("mLock") getActivityTokenLocked()1439 @NonNull IBinder getActivityTokenLocked() { 1440 return mActivityToken; 1441 } 1442 1443 /** 1444 * Sets new activity and client for this session. 1445 * 1446 * @param newActivity The token of the new activity 1447 * @param newClient The client receiving autofill callbacks 1448 */ switchActivity(@onNull IBinder newActivity, @NonNull IBinder newClient)1449 void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { 1450 synchronized (mLock) { 1451 if (mDestroyed) { 1452 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: " 1453 + id + " destroyed"); 1454 return; 1455 } 1456 mActivityToken = newActivity; 1457 setClientLocked(newClient); 1458 1459 // The tracked id are not persisted in the client, hence update them 1460 updateTrackedIdsLocked(); 1461 } 1462 } 1463 1464 @GuardedBy("mLock") setClientLocked(@onNull IBinder client)1465 private void setClientLocked(@NonNull IBinder client) { 1466 unlinkClientVultureLocked(); 1467 mClient = IAutoFillManagerClient.Stub.asInterface(client); 1468 mClientVulture = () -> { 1469 synchronized (mLock) { 1470 Slog.d(TAG, "handling death of " + mActivityToken + " when saving=" 1471 + mSessionFlags.mShowingSaveUi); 1472 if (mSessionFlags.mShowingSaveUi) { 1473 mUi.hideFillUi(this); 1474 } else { 1475 mUi.destroyAll(mPendingSaveUi, this, false); 1476 } 1477 } 1478 }; 1479 try { 1480 mClient.asBinder().linkToDeath(mClientVulture, 0); 1481 } catch (RemoteException e) { 1482 Slog.w(TAG, "could not set binder death listener on autofill client: " + e); 1483 mClientVulture = null; 1484 } 1485 } 1486 1487 @GuardedBy("mLock") unlinkClientVultureLocked()1488 private void unlinkClientVultureLocked() { 1489 if (mClient != null && mClientVulture != null) { 1490 final boolean unlinked = mClient.asBinder().unlinkToDeath(mClientVulture, 0); 1491 if (!unlinked) { 1492 Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken); 1493 } 1494 mClientVulture = null; 1495 } 1496 } 1497 1498 // FillServiceCallbacks 1499 @Override 1500 @SuppressWarnings("GuardedBy") onFillRequestSuccess(int requestId, @Nullable FillResponse response, @NonNull String servicePackageName, int requestFlags)1501 public void onFillRequestSuccess(int requestId, @Nullable FillResponse response, 1502 @NonNull String servicePackageName, int requestFlags) { 1503 final AutofillId[] fieldClassificationIds; 1504 1505 final LogMaker requestLog; 1506 1507 // Start a new FillResponse logger for the success case. 1508 mFillResponseEventLogger.startLogForNewResponse(); 1509 mFillResponseEventLogger.maybeSetRequestId(requestId); 1510 mFillResponseEventLogger.maybeSetAppPackageUid(uid); 1511 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS); 1512 mFillResponseEventLogger.startResponseProcessingTime(); 1513 // Time passed since session was created 1514 final long fillRequestReceivedRelativeTimestamp = 1515 SystemClock.elapsedRealtime() - mLatencyBaseTime; 1516 mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs( 1517 (int) (fillRequestReceivedRelativeTimestamp)); 1518 mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( 1519 (int) (fillRequestReceivedRelativeTimestamp)); 1520 mFillResponseEventLogger.maybeSetDetectionPreference(getDetectionPreferenceForLogging()); 1521 1522 synchronized (mLock) { 1523 if (mDestroyed) { 1524 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " 1525 + id + " destroyed"); 1526 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); 1527 mFillResponseEventLogger.logAndEndEvent(); 1528 return; 1529 } 1530 1531 1532 requestLog = mRequestLogs.get(requestId); 1533 if (requestLog != null) { 1534 requestLog.setType(MetricsEvent.TYPE_SUCCESS); 1535 } else { 1536 Slog.w(TAG, "onFillRequestSuccess(): no request log for id " + requestId); 1537 } 1538 if (response == null) { 1539 mFillResponseEventLogger.maybeSetTotalDatasetsProvided(0); 1540 if (requestLog != null) { 1541 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1); 1542 } 1543 processNullResponseLocked(requestId, requestFlags); 1544 return; 1545 } 1546 1547 // TODO: Check if this is required. We can still present datasets to the user even if 1548 // traditional field classification is disabled. 1549 fieldClassificationIds = response.getFieldClassificationIds(); 1550 if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) { 1551 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled"); 1552 processNullResponseLocked(requestId, requestFlags); 1553 return; 1554 } 1555 1556 mLastFillDialogTriggerIds = response.getFillDialogTriggerIds(); 1557 1558 final int flags = response.getFlags(); 1559 if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) { 1560 Slog.v(TAG, "Service requested to wait for delayed fill response."); 1561 registerDelayedFillBroadcastLocked(); 1562 } 1563 } 1564 1565 mService.setLastResponse(id, response); 1566 1567 synchronized (mLock) { 1568 if (mLogViewEntered) { 1569 mLogViewEntered = false; 1570 mService.logViewEntered(id, null); 1571 } 1572 } 1573 1574 1575 final long disableDuration = response.getDisableDuration(); 1576 final boolean autofillDisabled = disableDuration > 0; 1577 if (autofillDisabled) { 1578 final int flags = response.getFlags(); 1579 final boolean disableActivityOnly = 1580 (flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0; 1581 notifyDisableAutofillToClient(disableDuration, 1582 disableActivityOnly ? mComponentName : null); 1583 1584 if (disableActivityOnly) { 1585 mService.disableAutofillForActivity(mComponentName, disableDuration, 1586 id, mCompatMode); 1587 } else { 1588 mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, 1589 id, mCompatMode); 1590 } 1591 1592 synchronized (mLock) { 1593 mSessionFlags.mAutofillDisabled = true; 1594 1595 // Although "standard" autofill is disabled, it might still trigger augmented 1596 // autofill 1597 if (triggerAugmentedAutofillLocked(requestFlags) != null) { 1598 mSessionFlags.mAugmentedAutofillOnly = true; 1599 if (sDebug) { 1600 Slog.d(TAG, "Service disabled autofill for " + mComponentName 1601 + ", but session is kept for augmented autofill only"); 1602 } 1603 return; 1604 } 1605 } 1606 1607 if (sDebug) { 1608 final StringBuilder message = new StringBuilder("Service disabled autofill for ") 1609 .append(mComponentName) 1610 .append(": flags=").append(flags) 1611 .append(", duration="); 1612 TimeUtils.formatDuration(disableDuration, message); 1613 Slog.d(TAG, message.toString()); 1614 } 1615 } 1616 List<Dataset> datasetList = response.getDatasets(); 1617 if (((datasetList == null || datasetList.isEmpty()) && response.getAuthentication() == null) 1618 || autofillDisabled) { 1619 // Response is "empty" from a UI point of view, need to notify client. 1620 notifyUnavailableToClient( 1621 autofillDisabled ? AutofillManager.STATE_DISABLED_BY_SERVICE : 0, 1622 /* autofillableIds= */ null); 1623 synchronized (mLock) { 1624 mInlineSessionController.setInlineFillUiLocked( 1625 InlineFillUi.emptyUi(mCurrentViewId)); 1626 } 1627 } 1628 1629 if (requestLog != null) { 1630 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, 1631 response.getDatasets() == null ? 0 : response.getDatasets().size()); 1632 if (fieldClassificationIds != null) { 1633 requestLog.addTaggedData( 1634 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS, 1635 fieldClassificationIds.length); 1636 } 1637 } 1638 1639 int datasetCount = (datasetList == null) ? 0 : datasetList.size(); 1640 mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount); 1641 // It's possible that this maybe overwritten later on after PCC filtering. 1642 mFillResponseEventLogger.maybeSetAvailableCount(datasetCount); 1643 1644 // TODO(b/266379948): Ideally wait for PCC request to finish for a while more 1645 // (say 100ms) before proceeding further on. 1646 1647 processResponseLockedForPcc(response, response.getClientState(), requestFlags); 1648 mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); 1649 } 1650 1651 1652 @GuardedBy("mLock") processResponseLockedForPcc(@onNull FillResponse response, @Nullable Bundle newClientState, int flags)1653 private void processResponseLockedForPcc(@NonNull FillResponse response, 1654 @Nullable Bundle newClientState, int flags) { 1655 if (DBG) { 1656 Slog.d(TAG, "DBG: Initial response: " + response); 1657 } 1658 synchronized (mLock) { 1659 response = getEffectiveFillResponse(response); 1660 if (isEmptyResponse(response)) { 1661 // Treat it as a null response. 1662 processNullResponseLocked( 1663 response != null ? response.getRequestId() : 0, 1664 flags); 1665 return; 1666 } 1667 if (DBG) { 1668 Slog.d(TAG, "DBG: Processed response: " + response); 1669 } 1670 processResponseLocked(response, newClientState, flags); 1671 } 1672 } 1673 isEmptyResponse(FillResponse response)1674 private boolean isEmptyResponse(FillResponse response) { 1675 if (response == null) return true; 1676 SaveInfo saveInfo = response.getSaveInfo(); 1677 synchronized (mLock) { 1678 return ((response.getDatasets() == null || response.getDatasets().isEmpty()) 1679 && response.getAuthentication() == null 1680 && (saveInfo == null 1681 || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) 1682 && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) 1683 && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) 1684 && (ArrayUtils.isEmpty(response.getFieldClassificationIds()))); 1685 } 1686 } 1687 getEffectiveFillResponse(FillResponse response)1688 private FillResponse getEffectiveFillResponse(FillResponse response) { 1689 // TODO(b/266379948): label dataset source 1690 1691 DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer(); 1692 computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer); 1693 1694 if (DBG) { 1695 Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: " 1696 + autofillProviderContainer); 1697 } 1698 if (!mService.isPccClassificationEnabled()) { 1699 if (sVerbose) { 1700 Slog.v(TAG, "PCC classification is disabled"); 1701 } 1702 return createShallowCopy(response, autofillProviderContainer); 1703 } 1704 synchronized (mLock) { 1705 if (mClassificationState.mState != ClassificationState.STATE_RESPONSE 1706 || mClassificationState.mLastFieldClassificationResponse == null) { 1707 if (sVerbose) { 1708 Slog.v(TAG, "PCC classification no last response:" 1709 + (mClassificationState.mLastFieldClassificationResponse == null) 1710 + " ,ineligible state=" 1711 + (mClassificationState.mState != ClassificationState.STATE_RESPONSE)); 1712 } 1713 return createShallowCopy(response, autofillProviderContainer); 1714 } 1715 if (!mClassificationState.processResponse()) return response; 1716 } 1717 boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc(); 1718 boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback(); 1719 if (preferAutofillProvider && !shouldUseFallback) { 1720 if (sVerbose) { 1721 Slog.v(TAG, "preferAutofillProvider but no fallback"); 1722 } 1723 return createShallowCopy(response, autofillProviderContainer); 1724 } 1725 1726 if (DBG) { 1727 synchronized (mLock) { 1728 Slog.d(TAG, "DBG: ClassificationState: " + mClassificationState); 1729 } 1730 } 1731 DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer(); 1732 computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer); 1733 if (DBG) { 1734 Slog.d(TAG, "DBG: computeDatasetsForPccAndUpdateContainer: " + detectionPccContainer); 1735 } 1736 1737 DatasetComputationContainer resultContainer; 1738 if (preferAutofillProvider) { 1739 resultContainer = autofillProviderContainer; 1740 if (shouldUseFallback) { 1741 // add PCC datasets that are not detected by provider. 1742 addFallbackDatasets(autofillProviderContainer, detectionPccContainer); 1743 } 1744 } else { 1745 resultContainer = detectionPccContainer; 1746 if (shouldUseFallback) { 1747 // add Provider's datasets that are not detected by PCC. 1748 addFallbackDatasets(detectionPccContainer, autofillProviderContainer); 1749 } 1750 } 1751 // Create FillResponse with effectiveDatasets, and all the rest value from the original 1752 // response. 1753 return createShallowCopy(response, resultContainer); 1754 } 1755 createShallowCopy( FillResponse response, DatasetComputationContainer container)1756 private FillResponse createShallowCopy( 1757 FillResponse response, DatasetComputationContainer container) { 1758 return FillResponse.shallowCopy( 1759 response, 1760 new ArrayList<>(container.mDatasets), 1761 getEligibleSaveInfo(response)); 1762 } 1763 getEligibleSaveInfo(FillResponse response)1764 private SaveInfo getEligibleSaveInfo(FillResponse response) { 1765 SaveInfo saveInfo = response.getSaveInfo(); 1766 if (saveInfo == null || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds()) 1767 || !ArrayUtils.isEmpty(saveInfo.getRequiredIds()) 1768 || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) { 1769 return saveInfo; 1770 } 1771 synchronized (mLock) { 1772 ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap = 1773 mClassificationState.mHintsToAutofillIdMap; 1774 if (hintsToAutofillIdMap == null || hintsToAutofillIdMap.isEmpty()) { 1775 return saveInfo; 1776 } 1777 1778 ArraySet<AutofillId> ids = new ArraySet<>(); 1779 int saveType = saveInfo.getType(); 1780 if (saveType == SaveInfo.SAVE_DATA_TYPE_GENERIC) { 1781 for (Set<AutofillId> autofillIds: hintsToAutofillIdMap.values()) { 1782 ids.addAll(autofillIds); 1783 } 1784 } else { 1785 Set<String> hints = HintsHelper.getHintsForSaveType(saveType); 1786 for (Map.Entry<String, Set<AutofillId>> entry: hintsToAutofillIdMap.entrySet()) { 1787 String hint = entry.getKey(); 1788 if (hints.contains(hint)) { 1789 ids.addAll(entry.getValue()); 1790 } 1791 } 1792 } 1793 if (ids.isEmpty()) return saveInfo; 1794 AutofillId[] autofillIds = new AutofillId[ids.size()]; 1795 mSaveEventLogger.maybeSetIsFrameworkCreatedSaveInfo(true); 1796 ids.toArray(autofillIds); 1797 return SaveInfo.copy(saveInfo, autofillIds); 1798 } 1799 } 1800 1801 /** 1802 * A private class to hold & compute datasets to be shown 1803 */ 1804 private static class DatasetComputationContainer { 1805 // List of all autofill ids that have a corresponding datasets 1806 Set<AutofillId> mAutofillIds = new LinkedHashSet<>(); 1807 // Set of datasets. Kept separately, to be able to be used directly for composing 1808 // FillResponse. 1809 Set<Dataset> mDatasets = new LinkedHashSet<>(); 1810 Map<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new LinkedHashMap<>(); 1811 toString()1812 public String toString() { 1813 final StringBuilder builder = new StringBuilder("DatasetComputationContainer["); 1814 if (mAutofillIds != null) { 1815 builder.append(", autofillIds=").append(mAutofillIds); 1816 } 1817 if (mDatasets != null) { 1818 builder.append(", mDatasets=").append(mDatasets); 1819 } 1820 if (mAutofillIdToDatasetMap != null) { 1821 builder.append(", mAutofillIdToDatasetMap=").append(mAutofillIdToDatasetMap); 1822 } 1823 return builder.append(']').toString(); 1824 } 1825 } 1826 1827 // Adds fallback datasets to the first container. 1828 // This function will destruct and modify c2 container. addFallbackDatasets( DatasetComputationContainer c1, DatasetComputationContainer c2)1829 private void addFallbackDatasets( 1830 DatasetComputationContainer c1, DatasetComputationContainer c2) { 1831 for (AutofillId id : c2.mAutofillIds) { 1832 if (!c1.mAutofillIds.contains(id)) { 1833 1834 // Since c2 could be modified in a previous iteration, it's possible that all 1835 // datasets corresponding to it have been evaluated, and it's map no longer has 1836 // any more datasets left. Early return in this case. 1837 if (c2.mAutofillIdToDatasetMap.get(id).isEmpty()) return; 1838 1839 // For AutofillId id, do the following 1840 // 1. Add all the datasets corresponding to it to c1's dataset, and update c1 1841 // properly. 1842 // 2. All the datasets that were added should be removed from the other autofill 1843 // ids that were in this dataset. This prevents us from revisiting those datasets. 1844 // Although we are using Sets, and that'd avoid re-adding them, using this logic 1845 // for now to keep safe. TODO(b/266379948): Revisit this logic. 1846 1847 Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id); 1848 Set<Dataset> copyDatasets = new LinkedHashSet<>(datasets); 1849 c1.mAutofillIds.add(id); 1850 c1.mAutofillIdToDatasetMap.put(id, copyDatasets); 1851 c1.mDatasets.addAll(copyDatasets); 1852 1853 for (Dataset dataset : datasets) { 1854 for (AutofillId currentId : dataset.getFieldIds()) { 1855 if (currentId.equals(id)) continue; 1856 // For this id, we need to remove the dataset from it's map. 1857 c2.mAutofillIdToDatasetMap.get(currentId).remove(dataset); 1858 } 1859 } 1860 } 1861 } 1862 } 1863 1864 /** 1865 * Computes datasets that are eligible to be shown based on provider detections. 1866 * Datasets are populated in the provided container for them to be later merged with the 1867 * PCC eligible datasets based on preference strategy. 1868 * @param response 1869 * @param container 1870 */ computeDatasetsForProviderAndUpdateContainer( FillResponse response, DatasetComputationContainer container)1871 private void computeDatasetsForProviderAndUpdateContainer( 1872 FillResponse response, DatasetComputationContainer container) { 1873 @DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN; 1874 boolean isPccEnabled = mService.isPccClassificationEnabled(); 1875 if (isPccEnabled) { 1876 globalPickReason = PICK_REASON_PROVIDER_DETECTION_ONLY; 1877 } else { 1878 globalPickReason = PICK_REASON_NO_PCC; 1879 } 1880 List<Dataset> datasets = response.getDatasets(); 1881 if (datasets == null) return; 1882 Map<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new LinkedHashMap<>(); 1883 Set<Dataset> eligibleDatasets = new LinkedHashSet<>(); 1884 Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>(); 1885 for (Dataset dataset : response.getDatasets()) { 1886 if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue; 1887 @DatasetEligibleReason int pickReason = globalPickReason; 1888 if (dataset.getAutofillDatatypes() != null 1889 && !dataset.getAutofillDatatypes().isEmpty()) { 1890 // This dataset has information relevant for detection too, so we should filter 1891 // them out. It's possible that some fields are applicable to hints only, as such, 1892 // they need to be filtered off. 1893 // TODO(b/266379948): Verify the logic and add tests 1894 // Update dataset to only have non-null fieldValues 1895 1896 // Figure out if we need to process results. 1897 boolean conversionRequired = false; 1898 int newSize = dataset.getFieldIds().size(); 1899 for (AutofillId id : dataset.getFieldIds()) { 1900 if (id == null) { 1901 conversionRequired = true; 1902 newSize--; 1903 } 1904 } 1905 1906 // If the dataset doesn't have any non-null autofill id's, pass over. 1907 if (newSize == 0) continue; 1908 1909 if (conversionRequired) { 1910 pickReason = PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC; 1911 ArrayList<AutofillId> fieldIds = new ArrayList<>(newSize); 1912 ArrayList<AutofillValue> fieldValues = new ArrayList<>(newSize); 1913 ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(newSize); 1914 ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>(newSize); 1915 ArrayList<InlinePresentation> fieldInlinePresentations = 1916 new ArrayList<>(newSize); 1917 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = 1918 new ArrayList<>(newSize); 1919 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(newSize); 1920 1921 for (int i = 0; i < dataset.getFieldIds().size(); i++) { 1922 AutofillId id = dataset.getFieldIds().get(i); 1923 if (id != null) { 1924 // Copy over 1925 fieldIds.add(id); 1926 fieldValues.add(dataset.getFieldValues().get(i)); 1927 fieldPresentations.add(dataset.getFieldPresentation(i)); 1928 fieldDialogPresentations.add(dataset.getFieldDialogPresentation(i)); 1929 fieldInlinePresentations.add(dataset.getFieldInlinePresentation(i)); 1930 fieldInlineTooltipPresentations.add( 1931 dataset.getFieldInlineTooltipPresentation(i)); 1932 fieldFilters.add(dataset.getFilter(i)); 1933 } 1934 } 1935 dataset = 1936 new Dataset( 1937 fieldIds, 1938 fieldValues, 1939 fieldPresentations, 1940 fieldDialogPresentations, 1941 fieldInlinePresentations, 1942 fieldInlineTooltipPresentations, 1943 fieldFilters, 1944 new ArrayList<>(), 1945 dataset.getFieldContent(), 1946 null, 1947 null, 1948 null, 1949 null, 1950 dataset.getId(), 1951 dataset.getAuthentication()); 1952 } 1953 } 1954 dataset.setEligibleReasonReason(pickReason); 1955 eligibleDatasets.add(dataset); 1956 for (AutofillId id : dataset.getFieldIds()) { 1957 eligibleAutofillIds.add(id); 1958 Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id); 1959 if (datasetForIds == null) { 1960 datasetForIds = new LinkedHashSet<>(); 1961 } 1962 datasetForIds.add(dataset); 1963 autofillIdToDatasetMap.put(id, datasetForIds); 1964 } 1965 } 1966 container.mAutofillIdToDatasetMap = autofillIdToDatasetMap; 1967 container.mDatasets = eligibleDatasets; 1968 container.mAutofillIds = eligibleAutofillIds; 1969 } 1970 1971 /** 1972 * Computes datasets that are eligible to be shown based on PCC detections. 1973 * Datasets are populated in the provided container for them to be later merged with the 1974 * provider eligible datasets based on preference strategy. 1975 * @param response 1976 * @param container 1977 */ computeDatasetsForPccAndUpdateContainer( FillResponse response, DatasetComputationContainer container)1978 private void computeDatasetsForPccAndUpdateContainer( 1979 FillResponse response, DatasetComputationContainer container) { 1980 List<Dataset> datasets = response.getDatasets(); 1981 if (datasets == null) return; 1982 1983 synchronized (mLock) { 1984 Map<String, Set<AutofillId>> hintsToAutofillIdMap = 1985 mClassificationState.mHintsToAutofillIdMap; 1986 1987 // TODO(266379948): Handle group hints too. 1988 Map<String, Set<AutofillId>> groupHintsToAutofillIdMap = 1989 mClassificationState.mGroupHintsToAutofillIdMap; 1990 1991 Map<AutofillId, Set<Dataset>> map = new LinkedHashMap<>(); 1992 1993 Set<Dataset> eligibleDatasets = new LinkedHashSet<>(); 1994 Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>(); 1995 1996 for (int i = 0; i < datasets.size(); i++) { 1997 1998 @DatasetEligibleReason int pickReason = PICK_REASON_PCC_DETECTION_ONLY; 1999 Dataset dataset = datasets.get(i); 2000 if (dataset.getAutofillDatatypes() == null 2001 || dataset.getAutofillDatatypes().isEmpty()) continue; 2002 2003 ArrayList<AutofillId> fieldIds = new ArrayList<>(); 2004 ArrayList<AutofillValue> fieldValues = new ArrayList<>(); 2005 ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(); 2006 ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>(); 2007 ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>(); 2008 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>(); 2009 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(); 2010 Set<AutofillId> datasetAutofillIds = new LinkedHashSet<>(); 2011 2012 boolean isDatasetAvailable = false; 2013 Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>(); 2014 Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>(); 2015 2016 for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) { 2017 if (dataset.getAutofillDatatypes().get(j) == null) { 2018 // TODO : revisit pickReason logic 2019 if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) { 2020 pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER; 2021 } 2022 // Check if the autofill id at this index is detected by PCC. 2023 // If not, add that id here, otherwise, we can have duplicates when later 2024 // merging with provider datasets. 2025 // Howover, this doesn't make datasetAvailable for PCC on its own. 2026 // For that, there has to be a datatype detected by PCC, and the dataset 2027 // for that datatype provided by the provider. 2028 AutofillId autofillId = dataset.getFieldIds().get(j); 2029 if (!mClassificationState.mClassificationCombinedHintsMap 2030 .containsKey(autofillId)) { 2031 additionalEligibleAutofillIds.add(autofillId); 2032 additionalDatasetAutofillIds.add(autofillId); 2033 // For each of the field, copy over values. 2034 copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues, 2035 fieldPresentations, fieldDialogPresentations, 2036 fieldInlinePresentations, fieldInlineTooltipPresentations, 2037 fieldFilters); 2038 } 2039 continue; 2040 } 2041 String hint = dataset.getAutofillDatatypes().get(j); 2042 2043 if (hintsToAutofillIdMap.containsKey(hint)) { 2044 ArrayList<AutofillId> tempIds = 2045 new ArrayList<>(hintsToAutofillIdMap.get(hint)); 2046 if (tempIds.isEmpty()) { 2047 continue; 2048 } 2049 isDatasetAvailable = true; 2050 for (AutofillId autofillId : tempIds) { 2051 eligibleAutofillIds.add(autofillId); 2052 datasetAutofillIds.add(autofillId); 2053 // For each of the field, copy over values. 2054 copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues, 2055 fieldPresentations, fieldDialogPresentations, 2056 fieldInlinePresentations, fieldInlineTooltipPresentations, 2057 fieldFilters); 2058 } 2059 } 2060 // TODO(b/266379948): handle the case: 2061 // groupHintsToAutofillIdMap.containsKey(hint)) 2062 // but the autofill id not being applicable to other hints. 2063 // TODO(b/266379948): also handle the case where there could be more types in 2064 // the dataset, provided by the provider, however, they aren't applicable. 2065 } 2066 if (isDatasetAvailable) { 2067 datasetAutofillIds.addAll(additionalDatasetAutofillIds); 2068 eligibleAutofillIds.addAll(additionalEligibleAutofillIds); 2069 Dataset newDataset = 2070 new Dataset( 2071 fieldIds, 2072 fieldValues, 2073 fieldPresentations, 2074 fieldDialogPresentations, 2075 fieldInlinePresentations, 2076 fieldInlineTooltipPresentations, 2077 fieldFilters, 2078 new ArrayList<>(), 2079 dataset.getFieldContent(), 2080 null, 2081 null, 2082 null, 2083 null, 2084 dataset.getId(), 2085 dataset.getAuthentication()); 2086 newDataset.setEligibleReasonReason(pickReason); 2087 eligibleDatasets.add(newDataset); 2088 Set<Dataset> newDatasets; 2089 for (AutofillId autofillId : datasetAutofillIds) { 2090 if (map.containsKey(autofillId)) { 2091 newDatasets = map.get(autofillId); 2092 } else { 2093 newDatasets = new LinkedHashSet<>(); 2094 } 2095 newDatasets.add(newDataset); 2096 map.put(autofillId, newDatasets); 2097 } 2098 } 2099 } 2100 container.mAutofillIds = eligibleAutofillIds; 2101 container.mDatasets = eligibleDatasets; 2102 container.mAutofillIdToDatasetMap = map; 2103 } 2104 } 2105 copyFieldsFromDataset( Dataset dataset, int index, AutofillId autofillId, ArrayList<AutofillId> fieldIds, ArrayList<AutofillValue> fieldValues, ArrayList<RemoteViews> fieldPresentations, ArrayList<RemoteViews> fieldDialogPresentations, ArrayList<InlinePresentation> fieldInlinePresentations, ArrayList<InlinePresentation> fieldInlineTooltipPresentations, ArrayList<Dataset.DatasetFieldFilter> fieldFilters)2106 private void copyFieldsFromDataset( 2107 Dataset dataset, 2108 int index, 2109 AutofillId autofillId, 2110 ArrayList<AutofillId> fieldIds, 2111 ArrayList<AutofillValue> fieldValues, 2112 ArrayList<RemoteViews> fieldPresentations, 2113 ArrayList<RemoteViews> fieldDialogPresentations, 2114 ArrayList<InlinePresentation> fieldInlinePresentations, 2115 ArrayList<InlinePresentation> fieldInlineTooltipPresentations, 2116 ArrayList<Dataset.DatasetFieldFilter> fieldFilters) { 2117 // copy over values 2118 fieldIds.add(autofillId); 2119 fieldValues.add(dataset.getFieldValues().get(index)); 2120 // TODO(b/266379948): might need to make it more efficient by not 2121 // copying over value if it didn't exist. This would require creating 2122 // a getter for the presentations arraylist. 2123 fieldPresentations.add(dataset.getFieldPresentation(index)); 2124 fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index)); 2125 fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index)); 2126 fieldInlineTooltipPresentations.add( 2127 dataset.getFieldInlineTooltipPresentation(index)); 2128 fieldFilters.add(dataset.getFilter(index)); 2129 } 2130 2131 // FillServiceCallbacks 2132 @Override 2133 @SuppressWarnings("GuardedBy") onFillRequestFailure(int requestId, @Nullable CharSequence message)2134 public void onFillRequestFailure(int requestId, @Nullable CharSequence message) { 2135 onFillRequestFailureOrTimeout(requestId, false, message); 2136 } 2137 2138 // FillServiceCallbacks 2139 @Override 2140 @SuppressWarnings("GuardedBy") onFillRequestTimeout(int requestId)2141 public void onFillRequestTimeout(int requestId) { 2142 onFillRequestFailureOrTimeout(requestId, true, null); 2143 } 2144 2145 @SuppressWarnings("GuardedBy") onFillRequestFailureOrTimeout(int requestId, boolean timedOut, @Nullable CharSequence message)2146 private void onFillRequestFailureOrTimeout(int requestId, boolean timedOut, 2147 @Nullable CharSequence message) { 2148 boolean showMessage = !TextUtils.isEmpty(message); 2149 2150 // Start a new FillResponse logger for the failure or timeout case. 2151 mFillResponseEventLogger.startLogForNewResponse(); 2152 mFillResponseEventLogger.maybeSetRequestId(requestId); 2153 mFillResponseEventLogger.maybeSetAppPackageUid(uid); 2154 mFillResponseEventLogger.maybeSetAvailableCount( 2155 AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT); 2156 mFillResponseEventLogger.maybeSetTotalDatasetsProvided( 2157 AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT); 2158 mFillResponseEventLogger.maybeSetDetectionPreference(getDetectionPreferenceForLogging()); 2159 final long fillRequestReceivedRelativeTimestamp = 2160 SystemClock.elapsedRealtime() - mLatencyBaseTime; 2161 mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( 2162 (int)(fillRequestReceivedRelativeTimestamp)); 2163 2164 synchronized (mLock) { 2165 unregisterDelayedFillBroadcastLocked(); 2166 if (mDestroyed) { 2167 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId 2168 + ") rejected - session: " + id + " destroyed"); 2169 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); 2170 mFillResponseEventLogger.logAndEndEvent(); 2171 2172 return; 2173 } 2174 if (sDebug) { 2175 Slog.d(TAG, "finishing session due to service " 2176 + (timedOut ? "timeout" : "failure")); 2177 } 2178 mService.resetLastResponse(); 2179 mLastFillDialogTriggerIds = null; 2180 final LogMaker requestLog = mRequestLogs.get(requestId); 2181 if (requestLog == null) { 2182 Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId); 2183 } else { 2184 requestLog.setType(timedOut ? MetricsEvent.TYPE_CLOSE : MetricsEvent.TYPE_FAILURE); 2185 } 2186 if (showMessage) { 2187 final int targetSdk = mService.getTargedSdkLocked(); 2188 if (targetSdk >= Build.VERSION_CODES.Q) { 2189 showMessage = false; 2190 Slog.w(TAG, "onFillRequestFailureOrTimeout(): not showing '" + message 2191 + "' because service's targetting API " + targetSdk); 2192 } 2193 if (message != null) { 2194 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, 2195 message.length()); 2196 } 2197 } 2198 2199 if (timedOut) { 2200 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 2201 NOT_SHOWN_REASON_REQUEST_TIMEOUT); 2202 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_TIMEOUT); 2203 } else { 2204 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 2205 NOT_SHOWN_REASON_REQUEST_FAILED); 2206 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_FAILURE); 2207 } 2208 mPresentationStatsEventLogger.logAndEndEvent(); 2209 mFillResponseEventLogger.logAndEndEvent(); 2210 } 2211 notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED, 2212 /* autofillableIds= */ null); 2213 if (showMessage) { 2214 getUiForShowing().showError(message, this); 2215 } 2216 removeFromService(); 2217 } 2218 2219 // FillServiceCallbacks 2220 @Override onSaveRequestSuccess(@onNull String servicePackageName, @Nullable IntentSender intentSender)2221 public void onSaveRequestSuccess(@NonNull String servicePackageName, 2222 @Nullable IntentSender intentSender) { 2223 synchronized (mLock) { 2224 mSessionFlags.mShowingSaveUi = false; 2225 // Log onSaveRequest result. 2226 mSaveEventLogger.maybeSetIsSaved(true); 2227 final long saveRequestFinishTimestamp = 2228 SystemClock.elapsedRealtime() - mLatencyBaseTime; 2229 mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); 2230 mSaveEventLogger.logAndEndEvent(); 2231 if (mDestroyed) { 2232 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " 2233 + id + " destroyed"); 2234 return; 2235 } 2236 } 2237 LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) 2238 .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN); 2239 mMetricsLogger.write(log); 2240 2241 2242 if (intentSender != null) { 2243 if (sDebug) Slog.d(TAG, "Starting intent sender on save()"); 2244 startIntentSenderAndFinishSession(intentSender); 2245 } 2246 2247 // Nothing left to do... 2248 removeFromService(); 2249 } 2250 2251 // FillServiceCallbacks 2252 @Override onSaveRequestFailure(@ullable CharSequence message, @NonNull String servicePackageName)2253 public void onSaveRequestFailure(@Nullable CharSequence message, 2254 @NonNull String servicePackageName) { 2255 boolean showMessage = !TextUtils.isEmpty(message); 2256 2257 synchronized (mLock) { 2258 mSessionFlags.mShowingSaveUi = false; 2259 // Log onSaveRequest result. 2260 final long saveRequestFinishTimestamp = 2261 SystemClock.elapsedRealtime() - mLatencyBaseTime; 2262 mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); 2263 mSaveEventLogger.logAndEndEvent(); 2264 if (mDestroyed) { 2265 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " 2266 + id + " destroyed"); 2267 return; 2268 } 2269 if (showMessage) { 2270 final int targetSdk = mService.getTargedSdkLocked(); 2271 if (targetSdk >= Build.VERSION_CODES.Q) { 2272 showMessage = false; 2273 Slog.w(TAG, "onSaveRequestFailure(): not showing '" + message 2274 + "' because service's targetting API " + targetSdk); 2275 } 2276 } 2277 } 2278 final LogMaker log = 2279 newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) 2280 .setType(MetricsEvent.TYPE_FAILURE); 2281 if (message != null) { 2282 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length()); 2283 } 2284 mMetricsLogger.write(log); 2285 2286 2287 if (showMessage) { 2288 getUiForShowing().showError(message, this); 2289 } 2290 removeFromService(); 2291 } 2292 2293 /** 2294 * Gets the {@link FillContext} for a request. 2295 * 2296 * @param requestId The id of the request 2297 * 2298 * @return The context or {@code null} if there is no context 2299 */ 2300 @GuardedBy("mLock") getFillContextByRequestIdLocked(int requestId)2301 @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) { 2302 if (mContexts == null) { 2303 return null; 2304 } 2305 2306 int numContexts = mContexts.size(); 2307 for (int i = 0; i < numContexts; i++) { 2308 FillContext context = mContexts.get(i); 2309 2310 if (context.getRequestId() == requestId) { 2311 return context; 2312 } 2313 } 2314 2315 return null; 2316 } 2317 2318 // VultureCallback 2319 @Override onServiceDied(@onNull RemoteFillService service)2320 public void onServiceDied(@NonNull RemoteFillService service) { 2321 Slog.w(TAG, "removing session because service died"); 2322 synchronized (mLock) { 2323 forceRemoveFromServiceLocked(); 2324 } 2325 } 2326 2327 // AutoFillUiCallback 2328 @Override authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras, int uiType)2329 public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras, 2330 int uiType) { 2331 if (sDebug) { 2332 Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex 2333 + "; intentSender=" + intent); 2334 } 2335 final Intent fillInIntent; 2336 synchronized (mLock) { 2337 mPresentationStatsEventLogger.maybeSetAuthenticationType( 2338 AUTHENTICATION_TYPE_FULL_AUTHENTICATION); 2339 if (mDestroyed) { 2340 Slog.w(TAG, "Call to Session#authenticate() rejected - session: " 2341 + id + " destroyed"); 2342 return; 2343 } 2344 fillInIntent = createAuthFillInIntentLocked(requestId, extras); 2345 if (fillInIntent == null) { 2346 forceRemoveFromServiceLocked(); 2347 return; 2348 } 2349 } 2350 2351 mService.setAuthenticationSelected(id, mClientState, uiType); 2352 2353 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); 2354 mHandler.sendMessage(obtainMessage( 2355 Session::startAuthentication, 2356 this, authenticationId, intent, fillInIntent, 2357 /* authenticateInline= */ uiType == UI_TYPE_INLINE)); 2358 } 2359 2360 // AutoFillUiCallback 2361 @Override fill(int requestId, int datasetIndex, Dataset dataset, int uiType)2362 public void fill(int requestId, int datasetIndex, Dataset dataset, int uiType) { 2363 synchronized (mLock) { 2364 if (mDestroyed) { 2365 Slog.w(TAG, "Call to Session#fill() rejected - session: " 2366 + id + " destroyed"); 2367 return; 2368 } 2369 } 2370 mHandler.sendMessage(obtainMessage( 2371 Session::autoFill, 2372 this, requestId, datasetIndex, dataset, true, uiType)); 2373 } 2374 2375 // AutoFillUiCallback 2376 @Override save()2377 public void save() { 2378 synchronized (mLock) { 2379 if (mDestroyed) { 2380 Slog.w(TAG, "Call to Session#save() rejected - session: " 2381 + id + " destroyed"); 2382 return; 2383 } 2384 } 2385 final long saveRequestStartTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime; 2386 mSaveEventLogger.maybeSetLatencySaveRequestMillis(saveRequestStartTimestamp); 2387 mHandler.sendMessage(obtainMessage( 2388 AutofillManagerServiceImpl::handleSessionSave, 2389 mService, this)); 2390 } 2391 2392 // AutoFillUiCallback 2393 @Override cancelSave()2394 public void cancelSave() { 2395 synchronized (mLock) { 2396 mSessionFlags.mShowingSaveUi = false; 2397 if (mDestroyed) { 2398 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " 2399 + id + " destroyed"); 2400 return; 2401 } 2402 } 2403 mHandler.sendMessage(obtainMessage( 2404 Session::removeFromService, this)); 2405 } 2406 2407 // AutofillUiCallback 2408 @Override onShown(int uiType)2409 public void onShown(int uiType) { 2410 synchronized (mLock) { 2411 if (uiType == UI_TYPE_INLINE) { 2412 if (mLoggedInlineDatasetShown) { 2413 // Chip inflation already logged, do not log again. 2414 // This is needed because every chip inflation will call this. 2415 return; 2416 } 2417 mLoggedInlineDatasetShown = true; 2418 } 2419 mService.logDatasetShown(this.id, mClientState, uiType); 2420 } 2421 } 2422 2423 // AutoFillUiCallback 2424 @Override requestShowFillUi(AutofillId id, int width, int height, IAutofillWindowPresenter presenter)2425 public void requestShowFillUi(AutofillId id, int width, int height, 2426 IAutofillWindowPresenter presenter) { 2427 synchronized (mLock) { 2428 if (mDestroyed) { 2429 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: " 2430 + id + " destroyed"); 2431 return; 2432 } 2433 if (id.equals(mCurrentViewId)) { 2434 try { 2435 final ViewState view = mViewStates.get(id); 2436 mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(), 2437 presenter); 2438 } catch (RemoteException e) { 2439 Slog.e(TAG, "Error requesting to show fill UI", e); 2440 } 2441 } else { 2442 if (sDebug) { 2443 Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" 2444 + mCurrentViewId + ") anymore"); 2445 } 2446 } 2447 } 2448 } 2449 2450 // AutoFillUiCallback 2451 @Override dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent)2452 public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) { 2453 synchronized (mLock) { 2454 if (mDestroyed) { 2455 Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: " 2456 + id + " destroyed"); 2457 return; 2458 } 2459 if (id.equals(mCurrentViewId)) { 2460 try { 2461 mClient.dispatchUnhandledKey(this.id, id, keyEvent); 2462 } catch (RemoteException e) { 2463 Slog.e(TAG, "Error requesting to dispatch unhandled key", e); 2464 } 2465 } else { 2466 Slog.w(TAG, "Do not dispatch unhandled key on " + id 2467 + " as it is not the current view (" + mCurrentViewId + ") anymore"); 2468 } 2469 } 2470 } 2471 2472 // AutoFillUiCallback 2473 @Override requestHideFillUi(AutofillId id)2474 public void requestHideFillUi(AutofillId id) { 2475 synchronized (mLock) { 2476 // NOTE: We allow this call in a destroyed state as the UI is 2477 // asked to go away after we get destroyed, so let it do that. 2478 try { 2479 mClient.requestHideFillUi(this.id, id); 2480 } catch (RemoteException e) { 2481 Slog.e(TAG, "Error requesting to hide fill UI", e); 2482 } 2483 2484 mInlineSessionController.hideInlineSuggestionsUiLocked(id); 2485 } 2486 } 2487 2488 @Override requestHideFillUiWhenDestroyed(AutofillId id)2489 public void requestHideFillUiWhenDestroyed(AutofillId id) { 2490 synchronized (mLock) { 2491 // NOTE: We allow this call in a destroyed state as the UI is 2492 // asked to go away after we get destroyed, so let it do that. 2493 try { 2494 mClient.requestHideFillUiWhenDestroyed(this.id, id); 2495 } catch (RemoteException e) { 2496 Slog.e(TAG, "Error requesting to hide fill UI", e); 2497 } 2498 2499 mInlineSessionController.hideInlineSuggestionsUiLocked(id); 2500 } 2501 } 2502 2503 // AutoFillUiCallback 2504 @Override cancelSession()2505 public void cancelSession() { 2506 synchronized (mLock) { 2507 removeFromServiceLocked(); 2508 } 2509 } 2510 2511 // AutoFillUiCallback 2512 @Override startIntentSenderAndFinishSession(IntentSender intentSender)2513 public void startIntentSenderAndFinishSession(IntentSender intentSender) { 2514 startIntentSender(intentSender, null); 2515 } 2516 2517 // AutoFillUiCallback 2518 @Override startIntentSender(IntentSender intentSender, Intent intent)2519 public void startIntentSender(IntentSender intentSender, Intent intent) { 2520 synchronized (mLock) { 2521 if (mDestroyed) { 2522 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: " 2523 + id + " destroyed"); 2524 return; 2525 } 2526 if (intent == null) { 2527 removeFromServiceLocked(); 2528 } 2529 } 2530 mHandler.sendMessage(obtainMessage( 2531 Session::doStartIntentSender, 2532 this, intentSender, intent)); 2533 } 2534 2535 // AutoFillUiCallback 2536 @Override requestShowSoftInput(AutofillId id)2537 public void requestShowSoftInput(AutofillId id) { 2538 IAutoFillManagerClient client = getClient(); 2539 if (client != null) { 2540 try { 2541 client.requestShowSoftInput(id); 2542 } catch (RemoteException e) { 2543 Slog.e(TAG, "Error sending input show up notification", e); 2544 } 2545 } 2546 } 2547 2548 // AutoFillUiCallback 2549 @Override requestFallbackFromFillDialog()2550 public void requestFallbackFromFillDialog() { 2551 setFillDialogDisabled(); 2552 synchronized (mLock) { 2553 if (mCurrentViewId == null) { 2554 return; 2555 } 2556 final ViewState currentView = mViewStates.get(mCurrentViewId); 2557 currentView.maybeCallOnFillReady(mFlags); 2558 } 2559 } 2560 notifyFillUiHidden(@onNull AutofillId autofillId)2561 private void notifyFillUiHidden(@NonNull AutofillId autofillId) { 2562 synchronized (mLock) { 2563 try { 2564 mClient.notifyFillUiHidden(this.id, autofillId); 2565 } catch (RemoteException e) { 2566 Slog.e(TAG, "Error sending fill UI hidden notification", e); 2567 } 2568 } 2569 } 2570 notifyFillUiShown(@onNull AutofillId autofillId)2571 private void notifyFillUiShown(@NonNull AutofillId autofillId) { 2572 synchronized (mLock) { 2573 try { 2574 mClient.notifyFillUiShown(this.id, autofillId); 2575 } catch (RemoteException e) { 2576 Slog.e(TAG, "Error sending fill UI shown notification", e); 2577 } 2578 } 2579 } 2580 doStartIntentSender(IntentSender intentSender, Intent intent)2581 private void doStartIntentSender(IntentSender intentSender, Intent intent) { 2582 try { 2583 synchronized (mLock) { 2584 mClient.startIntentSender(intentSender, intent); 2585 } 2586 } catch (RemoteException e) { 2587 Slog.e(TAG, "Error launching auth intent", e); 2588 } 2589 } 2590 2591 @GuardedBy("mLock") setAuthenticationResultLocked(Bundle data, int authenticationId)2592 void setAuthenticationResultLocked(Bundle data, int authenticationId) { 2593 if (mDestroyed) { 2594 Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " 2595 + id + " destroyed"); 2596 return; 2597 } 2598 final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); 2599 if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) { 2600 setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId); 2601 // Augmented autofill is not logged. 2602 mPresentationStatsEventLogger.logAndEndEvent(); 2603 return; 2604 } 2605 if (mResponses == null) { 2606 // Typically happens when app explicitly called cancel() while the service was showing 2607 // the auth UI. 2608 Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses"); 2609 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2610 AUTHENTICATION_RESULT_FAILURE); 2611 mPresentationStatsEventLogger.logAndEndEvent(); 2612 removeFromService(); 2613 return; 2614 } 2615 final FillResponse authenticatedResponse = mResponses.get(requestId); 2616 if (authenticatedResponse == null || data == null) { 2617 Slog.w(TAG, "no authenticated response"); 2618 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2619 AUTHENTICATION_RESULT_FAILURE); 2620 mPresentationStatsEventLogger.logAndEndEvent(); 2621 removeFromService(); 2622 return; 2623 } 2624 2625 final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId( 2626 authenticationId); 2627 // Authenticated a dataset - reset view state regardless if we got a response or a dataset 2628 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { 2629 final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx); 2630 if (dataset == null) { 2631 Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response"); 2632 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2633 AUTHENTICATION_RESULT_FAILURE); 2634 mPresentationStatsEventLogger.logAndEndEvent(); 2635 removeFromService(); 2636 return; 2637 } 2638 } 2639 2640 // The client becomes invisible for the authentication, the response is effective. 2641 mSessionFlags.mExpiredResponse = false; 2642 2643 final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); 2644 final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); 2645 if (sDebug) { 2646 Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result 2647 + ", clientState=" + newClientState + ", authenticationId=" + authenticationId); 2648 } 2649 if (result instanceof FillResponse) { 2650 logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED); 2651 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2652 AUTHENTICATION_RESULT_SUCCESS); 2653 replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState); 2654 } else if (result instanceof Dataset) { 2655 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { 2656 logAuthenticationStatusLocked(requestId, 2657 MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); 2658 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2659 AUTHENTICATION_RESULT_SUCCESS); 2660 if (newClientState != null) { 2661 if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); 2662 mClientState = newClientState; 2663 } 2664 Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result); 2665 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx); 2666 if (!isAuthResultDatasetEphemeral(oldDataset, data)) { 2667 authenticatedResponse.getDatasets().set(datasetIdx, dataset); 2668 } 2669 autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN); 2670 } else { 2671 Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id " 2672 + authenticationId); 2673 logAuthenticationStatusLocked(requestId, 2674 MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION); 2675 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2676 AUTHENTICATION_RESULT_FAILURE); 2677 } 2678 } else { 2679 if (result != null) { 2680 Slog.w(TAG, "service returned invalid auth type: " + result); 2681 } 2682 logAuthenticationStatusLocked(requestId, 2683 MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION); 2684 mPresentationStatsEventLogger.maybeSetAuthenticationResult( 2685 AUTHENTICATION_RESULT_FAILURE); 2686 processNullResponseLocked(requestId, 0); 2687 } 2688 } 2689 getEffectiveDatasetForAuthentication(Dataset authenticatedDataset)2690 Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) { 2691 FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build(); 2692 response = getEffectiveFillResponse(response); 2693 if (DBG) { 2694 Slog.d(TAG, "DBG: authenticated effective response: " + response); 2695 } 2696 if (response == null || response.getDatasets().size() == 0) { 2697 Log.wtf(TAG, "No datasets in fill response on authentication. response = " 2698 + (response == null ? "null" : response.toString())); 2699 return authenticatedDataset; 2700 } 2701 List<Dataset> datasets = response.getDatasets(); 2702 Dataset result = response.getDatasets().get(0); 2703 if (datasets.size() > 1) { 2704 Dataset.Builder builder = new Dataset.Builder(); 2705 for (Dataset dataset : datasets) { 2706 if (!dataset.getFieldIds().isEmpty()) { 2707 for (int i = 0; i < dataset.getFieldIds().size(); i++) { 2708 builder.setField(dataset.getFieldIds().get(i), 2709 new Field.Builder().setValue(dataset.getFieldValues().get(i)) 2710 .build()); 2711 } 2712 } 2713 } 2714 result = builder.setId(authenticatedDataset.getId()).build(); 2715 } 2716 2717 if (DBG) { 2718 Slog.d(TAG, "DBG: authenticated effective dataset after auth: " + result); 2719 } 2720 return result; 2721 } 2722 2723 /** 2724 * Returns whether the dataset returned from the authentication result is ephemeral or not. 2725 * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more 2726 * information. 2727 */ isAuthResultDatasetEphemeral(@ullable Dataset oldDataset, @NonNull Bundle authResultData)2728 private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset, 2729 @NonNull Bundle authResultData) { 2730 if (authResultData.containsKey( 2731 AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) { 2732 return authResultData.getBoolean( 2733 AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET); 2734 } 2735 return isPinnedDataset(oldDataset); 2736 } 2737 2738 /** 2739 * A dataset can potentially have multiple fields, and it's possible that some of the fields' 2740 * has inline presentation and some don't. It's also possible that some of the fields' 2741 * inline presentation is pinned and some isn't. So the concept of whether a dataset is 2742 * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a 2743 * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient 2744 * for most of the cases. 2745 */ isPinnedDataset(@ullable Dataset dataset)2746 private static boolean isPinnedDataset(@Nullable Dataset dataset) { 2747 if (dataset != null && dataset.getFieldIds() != null) { 2748 final int numOfFields = dataset.getFieldIds().size(); 2749 for (int i = 0; i < numOfFields; i++) { 2750 final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(i); 2751 if (inlinePresentation != null && inlinePresentation.isPinned()) { 2752 return true; 2753 } 2754 } 2755 } 2756 return false; 2757 } 2758 2759 @GuardedBy("mLock") setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId)2760 void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) { 2761 final Dataset dataset = (data == null) ? null : 2762 data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT, android.service.autofill.Dataset.class); 2763 if (sDebug) { 2764 Slog.d(TAG, "Auth result for augmented autofill: sessionId=" + id 2765 + ", authId=" + authId + ", dataset=" + dataset); 2766 } 2767 final AutofillId fieldId = (dataset != null && dataset.getFieldIds().size() == 1) 2768 ? dataset.getFieldIds().get(0) : null; 2769 final AutofillValue value = (dataset != null && dataset.getFieldValues().size() == 1) 2770 ? dataset.getFieldValues().get(0) : null; 2771 final ClipData content = (dataset != null) ? dataset.getFieldContent() : null; 2772 if (fieldId == null || (value == null && content == null)) { 2773 if (sDebug) { 2774 Slog.d(TAG, "Rejecting empty/invalid auth result"); 2775 } 2776 mService.resetLastAugmentedAutofillResponse(); 2777 removeFromServiceLocked(); 2778 return; 2779 } 2780 2781 // Get a handle to the RemoteAugmentedAutofillService. In 2782 // AutofillManagerServiceImpl.updateRemoteAugmentedAutofillService() we invalidate sessions 2783 // whenever the service changes, so there should never be a case when we get here and the 2784 // remote service instance is not present or different. 2785 final RemoteAugmentedAutofillService remoteAugmentedAutofillService = 2786 mService.getRemoteAugmentedAutofillServiceIfCreatedLocked(); 2787 if (remoteAugmentedAutofillService == null) { 2788 Slog.e(TAG, "Can't fill after auth: RemoteAugmentedAutofillService is null"); 2789 mService.resetLastAugmentedAutofillResponse(); 2790 removeFromServiceLocked(); 2791 return; 2792 } 2793 2794 // Update state to ensure that after filling the field here we don't end up firing another 2795 // autofill request that will end up showing the same suggestions to the user again. When 2796 // the auth activity came up, the field for which the suggestions were shown lost focus and 2797 // mCurrentViewId was cleared. We need to set mCurrentViewId back to the id of the field 2798 // that we are filling. 2799 fieldId.setSessionId(id); 2800 mCurrentViewId = fieldId; 2801 2802 // Notify the Augmented Autofill provider of the dataset that was selected. 2803 final Bundle clientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); 2804 mService.logAugmentedAutofillSelected(id, dataset.getId(), clientState); 2805 2806 // For any content URIs, grant URI permissions to the target app before filling. 2807 if (content != null) { 2808 final AutofillUriGrantsManager autofillUgm = 2809 remoteAugmentedAutofillService.getAutofillUriGrantsManager(); 2810 autofillUgm.grantUriPermissions(mComponentName, mActivityToken, userId, content); 2811 } 2812 2813 // Fill the value into the field. 2814 if (sDebug) { 2815 Slog.d(TAG, "Filling after auth: fieldId=" + fieldId + ", value=" + value 2816 + ", content=" + content); 2817 } 2818 try { 2819 if (content != null) { 2820 mClient.autofillContent(id, fieldId, content); 2821 } else { 2822 mClient.autofill(id, dataset.getFieldIds(), dataset.getFieldValues(), true); 2823 } 2824 } catch (RemoteException e) { 2825 Slog.w(TAG, "Error filling after auth: fieldId=" + fieldId + ", value=" + value 2826 + ", content=" + content, e); 2827 } 2828 2829 // Clear the suggestions since the user already accepted one of them. 2830 mInlineSessionController.setInlineFillUiLocked(InlineFillUi.emptyUi(fieldId)); 2831 } 2832 2833 @GuardedBy("mLock") setHasCallbackLocked(boolean hasIt)2834 void setHasCallbackLocked(boolean hasIt) { 2835 if (mDestroyed) { 2836 Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " 2837 + id + " destroyed"); 2838 return; 2839 } 2840 mHasCallback = hasIt; 2841 } 2842 2843 @GuardedBy("mLock") 2844 @Nullable getLastResponseLocked(@ullable String logPrefixFmt)2845 private FillResponse getLastResponseLocked(@Nullable String logPrefixFmt) { 2846 final String logPrefix = sDebug && logPrefixFmt != null 2847 ? String.format(logPrefixFmt, this.id) 2848 : null; 2849 if (mContexts == null) { 2850 if (logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts"); 2851 return null; 2852 } 2853 if (mResponses == null) { 2854 // Happens when the activity / session was finished before the service replied, or 2855 // when the service cannot autofill it (and returned a null response). 2856 if (sVerbose && logPrefix != null) { 2857 Slog.v(TAG, logPrefix + ": no responses on session"); 2858 } 2859 return null; 2860 } 2861 2862 final int lastResponseIdx = getLastResponseIndexLocked(); 2863 if (lastResponseIdx < 0) { 2864 if (logPrefix != null) { 2865 Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses 2866 + ", mViewStates=" + mViewStates); 2867 } 2868 return null; 2869 } 2870 2871 final FillResponse response = mResponses.valueAt(lastResponseIdx); 2872 if (sVerbose && logPrefix != null) { 2873 Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts 2874 + ", mViewStates=" + mViewStates); 2875 } 2876 return response; 2877 } 2878 2879 @GuardedBy("mLock") 2880 @Nullable getSaveInfoLocked()2881 private SaveInfo getSaveInfoLocked() { 2882 final FillResponse response = getLastResponseLocked(null); 2883 return response == null ? null : response.getSaveInfo(); 2884 } 2885 2886 @GuardedBy("mLock") getSaveInfoFlagsLocked()2887 int getSaveInfoFlagsLocked() { 2888 final SaveInfo saveInfo = getSaveInfoLocked(); 2889 return saveInfo == null ? 0 : saveInfo.getFlags(); 2890 } 2891 2892 /** 2893 * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} 2894 * when necessary. 2895 */ logContextCommitted()2896 public void logContextCommitted() { 2897 mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, 2898 Event.NO_SAVE_UI_REASON_NONE, 2899 COMMIT_REASON_UNKNOWN)); 2900 logAllEvents(COMMIT_REASON_UNKNOWN); 2901 } 2902 2903 /** 2904 * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} 2905 * when necessary. 2906 * 2907 * @param saveDialogNotShowReason The reason why a save dialog was not shown. 2908 * @param commitReason The reason why context is committed. 2909 */ logContextCommitted(@oSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason)2910 public void logContextCommitted(@NoSaveReason int saveDialogNotShowReason, 2911 @AutofillCommitReason int commitReason) { 2912 mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, 2913 saveDialogNotShowReason, commitReason)); 2914 logAllEvents(commitReason); 2915 } 2916 handleLogContextCommitted(@oSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason)2917 private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason, 2918 @AutofillCommitReason int commitReason) { 2919 final FillResponse lastResponse; 2920 synchronized (mLock) { 2921 lastResponse = getLastResponseLocked("logContextCommited(%s)"); 2922 } 2923 2924 if (lastResponse == null) { 2925 Slog.w(TAG, "handleLogContextCommitted(): last response is null"); 2926 return; 2927 } 2928 2929 // Merge UserData if necessary. 2930 // Fields in packageUserData will override corresponding fields in genericUserData. 2931 final UserData genericUserData = mService.getUserData(); 2932 final UserData packageUserData = lastResponse.getUserData(); 2933 final FieldClassificationUserData userData; 2934 if (packageUserData == null && genericUserData == null) { 2935 userData = null; 2936 } else if (packageUserData != null && genericUserData != null) { 2937 userData = new CompositeUserData(genericUserData, packageUserData); 2938 } else if (packageUserData != null) { 2939 userData = packageUserData; 2940 } else { 2941 userData = mService.getUserData(); 2942 } 2943 2944 final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy(); 2945 2946 // Sets field classification scores 2947 if (userData != null && fcStrategy != null) { 2948 logFieldClassificationScore(fcStrategy, userData, saveDialogNotShowReason, 2949 commitReason); 2950 } else { 2951 logContextCommitted(null, null, saveDialogNotShowReason, commitReason); 2952 } 2953 } 2954 logContextCommitted(@ullable ArrayList<AutofillId> detectedFieldIds, @Nullable ArrayList<FieldClassification> detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason)2955 private void logContextCommitted(@Nullable ArrayList<AutofillId> detectedFieldIds, 2956 @Nullable ArrayList<FieldClassification> detectedFieldClassifications, 2957 @NoSaveReason int saveDialogNotShowReason, 2958 @AutofillCommitReason int commitReason) { 2959 synchronized (mLock) { 2960 logContextCommittedLocked(detectedFieldIds, detectedFieldClassifications, 2961 saveDialogNotShowReason, commitReason); 2962 } 2963 } 2964 2965 @GuardedBy("mLock") logContextCommittedLocked(@ullable ArrayList<AutofillId> detectedFieldIds, @Nullable ArrayList<FieldClassification> detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason)2966 private void logContextCommittedLocked(@Nullable ArrayList<AutofillId> detectedFieldIds, 2967 @Nullable ArrayList<FieldClassification> detectedFieldClassifications, 2968 @NoSaveReason int saveDialogNotShowReason, 2969 @AutofillCommitReason int commitReason) { 2970 final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)"); 2971 if (lastResponse == null) return; 2972 2973 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 2974 PresentationStatsEventLogger.getNoPresentationEventReason(commitReason)); 2975 mPresentationStatsEventLogger.logAndEndEvent(); 2976 2977 final int flags = lastResponse.getFlags(); 2978 if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) { 2979 if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags); 2980 return; 2981 } 2982 2983 ArraySet<String> ignoredDatasets = null; 2984 ArrayList<AutofillId> changedFieldIds = null; 2985 ArrayList<String> changedDatasetIds = null; 2986 ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null; 2987 2988 boolean hasAtLeastOneDataset = false; 2989 final int responseCount = mResponses.size(); 2990 for (int i = 0; i < responseCount; i++) { 2991 final FillResponse response = mResponses.valueAt(i); 2992 final List<Dataset> datasets = response.getDatasets(); 2993 if (datasets == null || datasets.isEmpty()) { 2994 if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i); 2995 } else { 2996 for (int j = 0; j < datasets.size(); j++) { 2997 final Dataset dataset = datasets.get(j); 2998 final String datasetId = dataset.getId(); 2999 if (datasetId == null) { 3000 if (sVerbose) { 3001 Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset); 3002 } 3003 } else { 3004 hasAtLeastOneDataset = true; 3005 if (mSelectedDatasetIds == null 3006 || !mSelectedDatasetIds.contains(datasetId)) { 3007 if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId); 3008 if (ignoredDatasets == null) { 3009 ignoredDatasets = new ArraySet<>(); 3010 } 3011 ignoredDatasets.add(datasetId); 3012 } 3013 } 3014 } 3015 } 3016 } 3017 3018 for (int i = 0; i < mViewStates.size(); i++) { 3019 final ViewState viewState = mViewStates.valueAt(i); 3020 final int state = viewState.getState(); 3021 3022 // When value changed, we need to log if it was: 3023 // - autofilled -> changedDatasetIds 3024 // - not autofilled but matches a dataset value -> manuallyFilledIds 3025 if ((state & ViewState.STATE_CHANGED) != 0) { 3026 // Check if autofilled value was changed 3027 if ((state & ViewState.STATE_AUTOFILLED_ONCE) != 0) { 3028 final String datasetId = viewState.getDatasetId(); 3029 if (datasetId == null) { 3030 // Validation check - should never happen. 3031 Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState); 3032 continue; 3033 } 3034 3035 // Must first check if final changed value is not the same as value sent by 3036 // service. 3037 final AutofillValue autofilledValue = viewState.getAutofilledValue(); 3038 final AutofillValue currentValue = viewState.getCurrentValue(); 3039 if (autofilledValue != null && autofilledValue.equals(currentValue)) { 3040 if (sDebug) { 3041 Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState 3042 + " because it has same value that was autofilled"); 3043 } 3044 continue; 3045 } 3046 3047 if (sDebug) { 3048 Slog.d(TAG, "logContextCommitted() found changed state: " + viewState); 3049 } 3050 if (changedFieldIds == null) { 3051 changedFieldIds = new ArrayList<>(); 3052 changedDatasetIds = new ArrayList<>(); 3053 } 3054 changedFieldIds.add(viewState.id); 3055 changedDatasetIds.add(datasetId); 3056 } else { 3057 final AutofillValue currentValue = viewState.getCurrentValue(); 3058 if (currentValue == null) { 3059 if (sDebug) { 3060 Slog.d(TAG, "logContextCommitted(): skipping view without current " 3061 + "value ( " + viewState + ")"); 3062 } 3063 continue; 3064 } 3065 3066 // Check if value match a dataset. 3067 if (hasAtLeastOneDataset) { 3068 for (int j = 0; j < responseCount; j++) { 3069 final FillResponse response = mResponses.valueAt(j); 3070 final List<Dataset> datasets = response.getDatasets(); 3071 if (datasets == null || datasets.isEmpty()) { 3072 if (sVerbose) { 3073 Slog.v(TAG, "logContextCommitted() no datasets at " + j); 3074 } 3075 } else { 3076 for (int k = 0; k < datasets.size(); k++) { 3077 final Dataset dataset = datasets.get(k); 3078 final String datasetId = dataset.getId(); 3079 if (datasetId == null) { 3080 if (sVerbose) { 3081 Slog.v(TAG, "logContextCommitted() skipping idless " 3082 + "dataset " + dataset); 3083 } 3084 } else { 3085 final ArrayList<AutofillValue> values = 3086 dataset.getFieldValues(); 3087 for (int l = 0; l < values.size(); l++) { 3088 final AutofillValue candidate = values.get(l); 3089 if (currentValue.equals(candidate)) { 3090 if (sDebug) { 3091 Slog.d(TAG, "field " + viewState.id + " was " 3092 + "manually filled with value set by " 3093 + "dataset " + datasetId); 3094 } 3095 if (manuallyFilledIds == null) { 3096 manuallyFilledIds = new ArrayMap<>(); 3097 } 3098 ArraySet<String> datasetIds = 3099 manuallyFilledIds.get(viewState.id); 3100 if (datasetIds == null) { 3101 datasetIds = new ArraySet<>(1); 3102 manuallyFilledIds.put(viewState.id, datasetIds); 3103 } 3104 datasetIds.add(datasetId); 3105 } 3106 } // for l 3107 if (mSelectedDatasetIds == null 3108 || !mSelectedDatasetIds.contains(datasetId)) { 3109 if (sVerbose) { 3110 Slog.v(TAG, "adding ignored dataset " + datasetId); 3111 } 3112 if (ignoredDatasets == null) { 3113 ignoredDatasets = new ArraySet<>(); 3114 } 3115 ignoredDatasets.add(datasetId); 3116 } // if 3117 } // if 3118 } // for k 3119 } // else 3120 } // for j 3121 } 3122 } // else 3123 } // else 3124 } 3125 3126 ArrayList<AutofillId> manuallyFilledFieldIds = null; 3127 ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null; 3128 3129 // Must "flatten" the map to the parcelable collection primitives 3130 if (manuallyFilledIds != null) { 3131 final int size = manuallyFilledIds.size(); 3132 manuallyFilledFieldIds = new ArrayList<>(size); 3133 manuallyFilledDatasetIds = new ArrayList<>(size); 3134 for (int i = 0; i < size; i++) { 3135 final AutofillId fieldId = manuallyFilledIds.keyAt(i); 3136 final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i); 3137 manuallyFilledFieldIds.add(fieldId); 3138 manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds)); 3139 } 3140 } 3141 3142 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets, 3143 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 3144 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, 3145 mComponentName, mCompatMode, saveDialogNotShowReason); 3146 logAllEvents(commitReason); 3147 } 3148 3149 /** 3150 * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for 3151 * {@code fieldId} based on its {@code currentValue} and {@code userData}. 3152 */ logFieldClassificationScore(@onNull FieldClassificationStrategy fcStrategy, @NonNull FieldClassificationUserData userData, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason)3153 private void logFieldClassificationScore(@NonNull FieldClassificationStrategy fcStrategy, 3154 @NonNull FieldClassificationUserData userData, 3155 @NoSaveReason int saveDialogNotShowReason, 3156 @AutofillCommitReason int commitReason) { 3157 3158 final String[] userValues = userData.getValues(); 3159 final String[] categoryIds = userData.getCategoryIds(); 3160 3161 final String defaultAlgorithm = userData.getFieldClassificationAlgorithm(); 3162 final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs(); 3163 3164 final ArrayMap<String, String> algorithms = userData.getFieldClassificationAlgorithms(); 3165 final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs(); 3166 3167 // Validation check 3168 if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) { 3169 final int valuesLength = userValues == null ? -1 : userValues.length; 3170 final int idsLength = categoryIds == null ? -1 : categoryIds.length; 3171 Slog.w(TAG, "setScores(): user data mismatch: values.length = " 3172 + valuesLength + ", ids.length = " + idsLength); 3173 return; 3174 } 3175 3176 final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize(); 3177 3178 final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize); 3179 final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>( 3180 maxFieldsSize); 3181 3182 final Collection<ViewState> viewStates; 3183 synchronized (mLock) { 3184 viewStates = mViewStates.values(); 3185 } 3186 3187 final int viewsSize = viewStates.size(); 3188 3189 // First, we get all scores. 3190 final AutofillId[] autofillIds = new AutofillId[viewsSize]; 3191 final ArrayList<AutofillValue> currentValues = new ArrayList<>(viewsSize); 3192 int k = 0; 3193 for (ViewState viewState : viewStates) { 3194 currentValues.add(viewState.getCurrentValue()); 3195 autofillIds[k++] = viewState.id; 3196 } 3197 3198 // Then use the results, asynchronously 3199 final RemoteCallback callback = new RemoteCallback( 3200 new LogFieldClassificationScoreOnResultListener( 3201 this, 3202 saveDialogNotShowReason, 3203 commitReason, 3204 viewsSize, 3205 autofillIds, 3206 userValues, 3207 categoryIds, 3208 detectedFieldIds, 3209 detectedFieldClassifications)); 3210 3211 fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, 3212 defaultAlgorithm, defaultArgs, algorithms, args); 3213 } 3214 handleLogFieldClassificationScore(@ullable Bundle result, int saveDialogNotShowReason, int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, String[] categoryIds, ArrayList<AutofillId> detectedFieldIds, ArrayList<FieldClassification> detectedFieldClassifications)3215 void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason, 3216 int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, 3217 String[] categoryIds, ArrayList<AutofillId> detectedFieldIds, 3218 ArrayList<FieldClassification> detectedFieldClassifications) { 3219 if (result == null) { 3220 if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); 3221 logContextCommitted(null, null, saveDialogNotShowReason, commitReason); 3222 return; 3223 } 3224 final Scores scores = result.getParcelable(EXTRA_SCORES, 3225 android.service.autofill.AutofillFieldClassificationService.Scores.class); 3226 if (scores == null) { 3227 Slog.w(TAG, "No field classification score on " + result); 3228 return; 3229 } 3230 int i = 0, j = 0; 3231 try { 3232 // Iteract over all autofill fields first 3233 for (i = 0; i < viewsSize; i++) { 3234 final AutofillId autofillId = autofillIds[i]; 3235 3236 // Search the best scores for each category (as some categories could have 3237 // multiple user values 3238 ArrayMap<String, Float> scoresByField = null; 3239 for (j = 0; j < userValues.length; j++) { 3240 final String categoryId = categoryIds[j]; 3241 final float score = scores.scores[i][j]; 3242 if (score > 0) { 3243 if (scoresByField == null) { 3244 scoresByField = new ArrayMap<>(userValues.length); 3245 } 3246 final Float currentScore = scoresByField.get(categoryId); 3247 if (currentScore != null && currentScore > score) { 3248 if (sVerbose) { 3249 Slog.v(TAG, "skipping score " + score 3250 + " because it's less than " + currentScore); 3251 } 3252 continue; 3253 } 3254 if (sVerbose) { 3255 Slog.v(TAG, "adding score " + score + " at index " + j + " and id " 3256 + autofillId); 3257 } 3258 scoresByField.put(categoryId, score); 3259 } else if (sVerbose) { 3260 Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId); 3261 } 3262 } 3263 if (scoresByField == null) { 3264 if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId); 3265 continue; 3266 } 3267 3268 // Then create the matches for that autofill id 3269 final ArrayList<Match> matches = new ArrayList<>(scoresByField.size()); 3270 for (j = 0; j < scoresByField.size(); j++) { 3271 final String fieldId = scoresByField.keyAt(j); 3272 final float score = scoresByField.valueAt(j); 3273 matches.add(new Match(fieldId, score)); 3274 } 3275 detectedFieldIds.add(autofillId); 3276 detectedFieldClassifications.add(new FieldClassification(matches)); 3277 } // for i 3278 } catch (ArrayIndexOutOfBoundsException e) { 3279 wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); 3280 return; 3281 } 3282 logContextCommitted(detectedFieldIds, detectedFieldClassifications, 3283 saveDialogNotShowReason, commitReason); 3284 } 3285 3286 /** 3287 * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} 3288 * when necessary. 3289 * 3290 * <p>Note: It is necessary to call logContextCommitted() first before calling this method. 3291 */ logSaveUiShown()3292 public void logSaveUiShown() { 3293 mHandler.sendMessage(obtainMessage(Session::logSaveShown, this)); 3294 } 3295 3296 /** 3297 * Shows the save UI, when session can be saved. 3298 * 3299 * @return {@link SaveResult} that contains the save ui display status information. 3300 */ 3301 @GuardedBy("mLock") 3302 @NonNull showSaveLocked()3303 public SaveResult showSaveLocked() { 3304 if (mDestroyed) { 3305 Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: " 3306 + id + " destroyed"); 3307 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED); 3308 mSaveEventLogger.logAndEndEvent(); 3309 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false, 3310 Event.NO_SAVE_UI_REASON_NONE); 3311 } 3312 mSessionState = STATE_FINISHED; 3313 final FillResponse response = getLastResponseLocked("showSaveLocked(%s)"); 3314 final SaveInfo saveInfo = response == null ? null : response.getSaveInfo(); 3315 3316 /* 3317 * Don't show save if the session has credman field 3318 * 3319 * TODO: add a new enum NO_SAVE_UI_CREDMAN 3320 */ 3321 if (mSessionFlags.mScreenHasCredmanField) { 3322 if (sVerbose) { 3323 Slog.v(TAG, "Call to Session#showSaveLocked() rejected - " 3324 + "there is credman field in screen"); 3325 } 3326 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3327 Event.NO_SAVE_UI_REASON_NONE); 3328 } 3329 3330 /* 3331 * The Save dialog is only shown if all conditions below are met: 3332 * 3333 * - saveInfo is not null. 3334 * - autofillValue of all required ids is not null. 3335 * - autofillValue of at least one id (required or optional) has changed. 3336 * - there is no Dataset in the last FillResponse whose values of all dataset fields matches 3337 * the current values of all fields in the screen. 3338 * - server didn't ask to keep session alive 3339 */ 3340 if (saveInfo == null) { 3341 if (sVerbose) Slog.v(TAG, "showSaveLocked(" + this.id + "): no saveInfo from service"); 3342 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO); 3343 mSaveEventLogger.logAndEndEvent(); 3344 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3345 Event.NO_SAVE_UI_REASON_NO_SAVE_INFO); 3346 } 3347 3348 if ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0) { 3349 // TODO(b/113281366): log metrics 3350 if (sDebug) Slog.v(TAG, "showSaveLocked(" + this.id + "): service asked to delay save"); 3351 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG); 3352 mSaveEventLogger.logAndEndEvent(); 3353 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false, 3354 Event.NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG); 3355 } 3356 3357 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo); 3358 3359 // Cache used to make sure changed fields do not belong to a dataset. 3360 final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>(); 3361 // Savable (optional or required) ids that will be checked against the dataset ids. 3362 final ArraySet<AutofillId> savableIds = new ArraySet<>(); 3363 3364 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 3365 boolean allRequiredAreNotEmpty = true; 3366 boolean atLeastOneChanged = false; 3367 // If an autofilled field is changed, we need to change isUpdate to true so the proper UI is 3368 // shown. 3369 boolean isUpdate = false; 3370 if (requiredIds != null) { 3371 for (int i = 0; i < requiredIds.length; i++) { 3372 final AutofillId id = requiredIds[i]; 3373 if (id == null) { 3374 Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds)); 3375 continue; 3376 } 3377 savableIds.add(id); 3378 final ViewState viewState = mViewStates.get(id); 3379 if (viewState == null) { 3380 Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id); 3381 allRequiredAreNotEmpty = false; 3382 break; 3383 } 3384 3385 AutofillValue value = viewState.getCurrentValue(); 3386 if (value == null || value.isEmpty()) { 3387 final AutofillValue initialValue = getValueFromContextsLocked(id); 3388 if (initialValue != null) { 3389 if (sDebug) { 3390 Slog.d(TAG, "Value of required field " + id + " didn't change; " 3391 + "using initial value (" + initialValue + ") instead"); 3392 } 3393 value = initialValue; 3394 } else { 3395 if (sDebug) { 3396 Slog.d(TAG, "empty value for required " + id ); 3397 } 3398 allRequiredAreNotEmpty = false; 3399 break; 3400 } 3401 } 3402 3403 value = getSanitizedValue(sanitizers, id, value); 3404 if (value == null) { 3405 if (sDebug) { 3406 Slog.d(TAG, "value of required field " + id + " failed sanitization"); 3407 } 3408 allRequiredAreNotEmpty = false; 3409 break; 3410 } 3411 viewState.setSanitizedValue(value); 3412 currentValues.put(id, value); 3413 final AutofillValue filledValue = viewState.getAutofilledValue(); 3414 3415 if (!value.equals(filledValue)) { 3416 boolean changed = true; 3417 if (filledValue == null) { 3418 // Dataset was not autofilled, make sure initial value didn't change. 3419 final AutofillValue initialValue = getValueFromContextsLocked(id); 3420 if (initialValue != null && initialValue.equals(value)) { 3421 if (sDebug) { 3422 Slog.d(TAG, "id " + id + " is part of dataset but initial value " 3423 + "didn't change: " + value); 3424 } 3425 changed = false; 3426 } else { 3427 mSaveEventLogger.maybeSetIsNewField(true); 3428 } 3429 } else { 3430 isUpdate = true; 3431 } 3432 if (changed) { 3433 if (sDebug) { 3434 Slog.d(TAG, "found a change on required " + id + ": " + filledValue 3435 + " => " + value); 3436 } 3437 atLeastOneChanged = true; 3438 } 3439 } 3440 } 3441 } 3442 3443 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 3444 if (sVerbose) { 3445 Slog.v(TAG, "allRequiredAreNotEmpty: " + allRequiredAreNotEmpty + " hasOptional: " 3446 + (optionalIds != null)); 3447 } 3448 int saveDialogNotShowReason; 3449 if (!allRequiredAreNotEmpty) { 3450 saveDialogNotShowReason = Event.NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED; 3451 3452 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_HAS_EMPTY_REQUIRED); 3453 mSaveEventLogger.logAndEndEvent(); 3454 } else { 3455 // Must look up all optional ids in 2 scenarios: 3456 // - if no required id changed but an optional id did, it should trigger save / update 3457 // - if at least one required id changed but it was not part of a filled dataset, we 3458 // need to check if an optional id is part of a filled datased (in which case we show 3459 // Update instead of Save) 3460 if (optionalIds!= null && (!atLeastOneChanged || !isUpdate)) { 3461 // No change on required ids yet, look for changes on optional ids. 3462 for (int i = 0; i < optionalIds.length; i++) { 3463 final AutofillId id = optionalIds[i]; 3464 savableIds.add(id); 3465 final ViewState viewState = mViewStates.get(id); 3466 if (viewState == null) { 3467 Slog.w(TAG, "no ViewState for optional " + id); 3468 continue; 3469 } 3470 if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { 3471 final AutofillValue currentValue = viewState.getCurrentValue(); 3472 final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue); 3473 if (value == null) { 3474 if (sDebug) { 3475 Slog.d(TAG, "value of opt. field " + id + " failed sanitization"); 3476 } 3477 continue; 3478 } 3479 3480 currentValues.put(id, value); 3481 final AutofillValue filledValue = viewState.getAutofilledValue(); 3482 if (value != null && !value.equals(filledValue)) { 3483 if (sDebug) { 3484 Slog.d(TAG, "found a change on optional " + id + ": " + filledValue 3485 + " => " + value); 3486 } 3487 if (filledValue != null) { 3488 isUpdate = true; 3489 } else { 3490 mSaveEventLogger.maybeSetIsNewField(true); 3491 } 3492 atLeastOneChanged = true; 3493 } 3494 } else { 3495 // Update current values cache based on initial value 3496 final AutofillValue initialValue = getValueFromContextsLocked(id); 3497 if (sDebug) { 3498 Slog.d(TAG, "no current value for " + id + "; initial value is " 3499 + initialValue); 3500 } 3501 if (initialValue != null) { 3502 currentValues.put(id, initialValue); 3503 } 3504 } 3505 } 3506 } 3507 if (!atLeastOneChanged) { 3508 saveDialogNotShowReason = Event.NO_SAVE_UI_REASON_NO_VALUE_CHANGED; 3509 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_VALUE_CHANGED); 3510 mSaveEventLogger.logAndEndEvent(); 3511 } else { 3512 if (sDebug) { 3513 Slog.d(TAG, "at least one field changed, validate fields for save UI"); 3514 } 3515 final InternalValidator validator = saveInfo.getValidator(); 3516 if (validator != null) { 3517 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION); 3518 boolean isValid; 3519 try { 3520 isValid = validator.isValid(this); 3521 if (sDebug) Slog.d(TAG, validator + " returned " + isValid); 3522 log.setType(isValid 3523 ? MetricsEvent.TYPE_SUCCESS 3524 : MetricsEvent.TYPE_DISMISS); 3525 } catch (Exception e) { 3526 Slog.e(TAG, "Not showing save UI because validation failed:", e); 3527 log.setType(MetricsEvent.TYPE_FAILURE); 3528 mMetricsLogger.write(log); 3529 mSaveEventLogger.maybeSetSaveUiNotShownReason( 3530 NO_SAVE_REASON_FIELD_VALIDATION_FAILED); 3531 mSaveEventLogger.logAndEndEvent(); 3532 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3533 Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED); 3534 } 3535 3536 mMetricsLogger.write(log); 3537 if (!isValid) { 3538 Slog.i(TAG, "not showing save UI because fields failed validation"); 3539 mSaveEventLogger.maybeSetSaveUiNotShownReason( 3540 NO_SAVE_REASON_FIELD_VALIDATION_FAILED); 3541 mSaveEventLogger.logAndEndEvent(); 3542 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3543 Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED); 3544 } 3545 } 3546 3547 // Make sure the service doesn't have the fields already by checking the datasets 3548 // content. 3549 final List<Dataset> datasets = response.getDatasets(); 3550 if (datasets != null) { 3551 datasets_loop: for (int i = 0; i < datasets.size(); i++) { 3552 final Dataset dataset = datasets.get(i); 3553 final ArrayMap<AutofillId, AutofillValue> datasetValues = 3554 Helper.getFields(dataset); 3555 if (sVerbose) { 3556 Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i 3557 + ": " + dataset + "; savableIds=" + savableIds); 3558 } 3559 savable_ids_loop: for (int j = 0; j < savableIds.size(); j++) { 3560 final AutofillId id = savableIds.valueAt(j); 3561 final AutofillValue currentValue = currentValues.get(id); 3562 if (currentValue == null) { 3563 if (sDebug) { 3564 Slog.d(TAG, "dataset has value for field that is null: " + id); 3565 } 3566 continue savable_ids_loop; 3567 } 3568 final AutofillValue datasetValue = datasetValues.get(id); 3569 if (!currentValue.equals(datasetValue)) { 3570 if (sDebug) { 3571 Slog.d(TAG, "found a dataset change on id " + id + ": from " 3572 + datasetValue + " to " + currentValue); 3573 } 3574 continue datasets_loop; 3575 } 3576 if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id); 3577 } 3578 if (sDebug) { 3579 Slog.d(TAG, "ignoring Save UI because all fields match contents of " 3580 + "dataset #" + i + ": " + dataset); 3581 } 3582 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_DATASET_MATCH); 3583 mSaveEventLogger.logAndEndEvent(); 3584 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3585 Event.NO_SAVE_UI_REASON_DATASET_MATCH); 3586 } 3587 } 3588 3589 if (sDebug) { 3590 Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " 3591 + id + "!"); 3592 } 3593 3594 final IAutoFillManagerClient client = getClient(); 3595 mPendingSaveUi = new PendingUi(new Binder(), id, client); 3596 3597 final CharSequence serviceLabel; 3598 final Drawable serviceIcon; 3599 synchronized (mLock) { 3600 serviceIcon = getServiceIcon(response); 3601 serviceLabel = getServiceLabel(response); 3602 } 3603 if (serviceLabel == null || serviceIcon == null) { 3604 wtf(null, "showSaveLocked(): no service label or icon"); 3605 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE); 3606 mSaveEventLogger.logAndEndEvent(); 3607 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3608 Event.NO_SAVE_UI_REASON_NONE); 3609 } 3610 final long saveUiDisplayStartTimestamp = SystemClock.elapsedRealtime(); 3611 getUiForShowing().showSaveUi(serviceLabel, serviceIcon, 3612 mService.getServicePackageName(), saveInfo, this, 3613 mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode, 3614 response.getShowSaveDialogIcon(), mSaveEventLogger); 3615 mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis( 3616 SystemClock.elapsedRealtime()- saveUiDisplayStartTimestamp); 3617 if (client != null) { 3618 try { 3619 client.setSaveUiState(id, true); 3620 } catch (RemoteException e) { 3621 Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e); 3622 } 3623 } 3624 mSessionFlags.mShowingSaveUi = true; 3625 return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false, 3626 Event.NO_SAVE_UI_REASON_NONE); 3627 } 3628 } 3629 // Nothing changed... 3630 if (sDebug) { 3631 Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities." 3632 + "allRequiredAreNotNull=" + allRequiredAreNotEmpty 3633 + ", atLeastOneChanged=" + atLeastOneChanged); 3634 } 3635 return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, 3636 saveDialogNotShowReason); 3637 } 3638 logSaveShown()3639 private void logSaveShown() { 3640 mService.logSaveShown(id, mClientState); 3641 } 3642 3643 @Nullable getSanitizedValue( @ullable ArrayMap<AutofillId, InternalSanitizer> sanitizers, @NonNull AutofillId id, @Nullable AutofillValue value)3644 private AutofillValue getSanitizedValue( 3645 @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers, 3646 @NonNull AutofillId id, 3647 @Nullable AutofillValue value) { 3648 if (sanitizers == null || value == null) return value; 3649 3650 final ViewState state = mViewStates.get(id); 3651 AutofillValue sanitized = state == null ? null : state.getSanitizedValue(); 3652 if (sanitized == null) { 3653 final InternalSanitizer sanitizer = sanitizers.get(id); 3654 if (sanitizer == null) { 3655 return value; 3656 } 3657 3658 sanitized = sanitizer.sanitize(value); 3659 if (sDebug) { 3660 Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized); 3661 } 3662 if (state != null) { 3663 state.setSanitizedValue(sanitized); 3664 } 3665 } 3666 return sanitized; 3667 } 3668 3669 /** 3670 * Returns whether the session is currently showing the save UI 3671 */ 3672 @GuardedBy("mLock") isSaveUiShowingLocked()3673 boolean isSaveUiShowingLocked() { 3674 return mSessionFlags.mShowingSaveUi; 3675 } 3676 3677 /** 3678 * Gets the latest non-empty value for the given id in the autofill contexts. 3679 */ 3680 @GuardedBy("mLock") 3681 @Nullable getValueFromContextsLocked(@onNull AutofillId autofillId)3682 private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) { 3683 final int numContexts = mContexts.size(); 3684 for (int i = numContexts - 1; i >= 0; i--) { 3685 final FillContext context = mContexts.get(i); 3686 final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), 3687 autofillId); 3688 if (node != null) { 3689 final AutofillValue value = node.getAutofillValue(); 3690 if (sDebug) { 3691 Slog.d(TAG, "getValueFromContexts(" + this.id + "/" + autofillId + ") at " 3692 + i + ": " + value); 3693 } 3694 if (value != null && !value.isEmpty()) { 3695 return value; 3696 } 3697 } 3698 } 3699 return null; 3700 } 3701 3702 /** 3703 * Gets the latest autofill options for the given id in the autofill contexts. 3704 */ 3705 @GuardedBy("mLock") 3706 @Nullable getAutofillOptionsFromContextsLocked(@onNull AutofillId autofillId)3707 private CharSequence[] getAutofillOptionsFromContextsLocked(@NonNull AutofillId autofillId) { 3708 final int numContexts = mContexts.size(); 3709 for (int i = numContexts - 1; i >= 0; i--) { 3710 final FillContext context = mContexts.get(i); 3711 final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), 3712 autofillId); 3713 if (node != null && node.getAutofillOptions() != null) { 3714 return node.getAutofillOptions(); 3715 } 3716 } 3717 return null; 3718 } 3719 3720 /** 3721 * Update the {@link AutofillValue values} of the {@link AssistStructure} before sending it to 3722 * the service on save(). 3723 */ updateValuesForSaveLocked()3724 private void updateValuesForSaveLocked() { 3725 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = 3726 createSanitizers(getSaveInfoLocked()); 3727 3728 final int numContexts = mContexts.size(); 3729 for (int contextNum = 0; contextNum < numContexts; contextNum++) { 3730 final FillContext context = mContexts.get(contextNum); 3731 3732 final ViewNode[] nodes = 3733 context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); 3734 3735 if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): updating " + context); 3736 3737 for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) { 3738 final ViewState viewState = mViewStates.valueAt(viewStateNum); 3739 3740 final AutofillId id = viewState.id; 3741 final AutofillValue value = viewState.getCurrentValue(); 3742 if (value == null) { 3743 if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): skipping " + id); 3744 continue; 3745 } 3746 final ViewNode node = nodes[viewStateNum]; 3747 if (node == null) { 3748 Slog.w(TAG, "callSaveLocked(): did not find node with id " + id); 3749 continue; 3750 } 3751 if (sVerbose) { 3752 Slog.v(TAG, "updateValuesForSaveLocked(): updating " + id + " to " + value); 3753 } 3754 3755 AutofillValue sanitizedValue = viewState.getSanitizedValue(); 3756 3757 if (sanitizedValue == null) { 3758 // Field is optional and haven't been sanitized yet. 3759 sanitizedValue = getSanitizedValue(sanitizers, id, value); 3760 } 3761 if (sanitizedValue != null) { 3762 node.updateAutofillValue(sanitizedValue); 3763 } else if (sDebug) { 3764 Slog.d(TAG, "updateValuesForSaveLocked(): not updating field " + id 3765 + " because it failed sanitization"); 3766 } 3767 } 3768 3769 // Sanitize structure before it's sent to service. 3770 context.getStructure().sanitizeForParceling(false); 3771 3772 if (sVerbose) { 3773 Slog.v(TAG, "updateValuesForSaveLocked(): dumping structure of " + context 3774 + " before calling service.save()"); 3775 context.getStructure().dump(false); 3776 } 3777 } 3778 } 3779 3780 /** 3781 * Calls service when user requested save. 3782 */ 3783 @GuardedBy("mLock") callSaveLocked()3784 void callSaveLocked() { 3785 if (mDestroyed) { 3786 Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " 3787 + id + " destroyed"); 3788 mSaveEventLogger.maybeSetIsSaved(false); 3789 mSaveEventLogger.logAndEndEvent(); 3790 return; 3791 } 3792 if (mRemoteFillService == null) { 3793 wtf(null, "callSaveLocked() called without a remote service. " 3794 + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); 3795 mSaveEventLogger.maybeSetIsSaved(false); 3796 mSaveEventLogger.logAndEndEvent(); 3797 return; 3798 } 3799 3800 if (sVerbose) Slog.v(TAG, "callSaveLocked(" + this.id + "): mViewStates=" + mViewStates); 3801 3802 if (mContexts == null) { 3803 Slog.w(TAG, "callSaveLocked(): no contexts"); 3804 mSaveEventLogger.maybeSetIsSaved(false); 3805 mSaveEventLogger.logAndEndEvent(); 3806 return; 3807 } 3808 3809 updateValuesForSaveLocked(); 3810 3811 // Remove pending fill requests as the session is finished. 3812 cancelCurrentRequestLocked(); 3813 3814 final ArrayList<FillContext> contexts = mergePreviousSessionLocked( /* forSave= */ true); 3815 3816 FieldClassificationResponse fieldClassificationResponse = 3817 mClassificationState.mLastFieldClassificationResponse; 3818 if (mService.isPccClassificationEnabled() 3819 && fieldClassificationResponse != null 3820 && !fieldClassificationResponse.getClassifications().isEmpty()) { 3821 if (mClientState == null) { 3822 mClientState = new Bundle(); 3823 } 3824 mClientState.putParcelableArrayList(EXTRA_KEY_DETECTIONS, new ArrayList<>( 3825 fieldClassificationResponse.getClassifications())); 3826 } 3827 final SaveRequest saveRequest = 3828 new SaveRequest(contexts, mClientState, mSelectedDatasetIds); 3829 mRemoteFillService.onSaveRequest(saveRequest); 3830 } 3831 3832 // TODO(b/113281366): rather than merge it here, it might be better to simply reuse the old 3833 // session instead of creating a new one. But we need to consider what would happen on corner 3834 // cases such as "Main Activity M -> activity A with username -> activity B with password" 3835 // If user follows the normal workflow, then session A would be merged with session B as 3836 // expected. But if when on Activity A the user taps back or somehow launches another activity, 3837 // session A could be merged with the wrong session. 3838 /** 3839 * Gets a list of contexts that includes not only this session's contexts but also the contexts 3840 * from previous sessions that were asked by the service to be delayed (if any). 3841 * 3842 * <p>As a side-effect: 3843 * <ul> 3844 * <li>If the current {@link #mClientState} is {@code null}, sets it with the last non- 3845 * {@code null} client state from previous sessions. 3846 * <li>When {@code forSave} is {@code true}, calls {@link #updateValuesForSaveLocked()} in the 3847 * previous sessions. 3848 * </ul> 3849 */ 3850 @NonNull mergePreviousSessionLocked(boolean forSave)3851 private ArrayList<FillContext> mergePreviousSessionLocked(boolean forSave) { 3852 final ArrayList<Session> previousSessions = mService.getPreviousSessionsLocked(this); 3853 final ArrayList<FillContext> contexts; 3854 if (previousSessions != null) { 3855 if (sDebug) { 3856 Slog.d(TAG, "mergeSessions(" + this.id + "): Merging the content of " 3857 + previousSessions.size() + " sessions for task " + taskId); 3858 } 3859 contexts = new ArrayList<>(); 3860 for (int i = 0; i < previousSessions.size(); i++) { 3861 final Session previousSession = previousSessions.get(i); 3862 final ArrayList<FillContext> previousContexts = previousSession.mContexts; 3863 if (previousContexts == null) { 3864 Slog.w(TAG, "mergeSessions(" + this.id + "): Not merging null contexts from " 3865 + previousSession.id); 3866 continue; 3867 } 3868 if (forSave) { 3869 previousSession.updateValuesForSaveLocked(); 3870 } 3871 if (sDebug) { 3872 Slog.d(TAG, "mergeSessions(" + this.id + "): adding " + previousContexts.size() 3873 + " context from previous session #" + previousSession.id); 3874 } 3875 contexts.addAll(previousContexts); 3876 if (mClientState == null && previousSession.mClientState != null) { 3877 if (sDebug) { 3878 Slog.d(TAG, "mergeSessions(" + this.id + "): setting client state from " 3879 + "previous session" + previousSession.id); 3880 } 3881 mClientState = previousSession.mClientState; 3882 } 3883 } 3884 contexts.addAll(mContexts); 3885 } else { 3886 // Dispatch a snapshot of the current contexts list since it may change 3887 // until the dispatch happens. The items in the list don't need to be cloned 3888 // since we don't hold on them anywhere else. The client state is not touched 3889 // by us, so no need to copy. 3890 contexts = new ArrayList<>(mContexts); 3891 } 3892 return contexts; 3893 } 3894 3895 /** 3896 * Starts (if necessary) a new fill request upon entering a view. 3897 * 3898 * <p>A new request will be started in 2 scenarios: 3899 * <ol> 3900 * <li>If the user manually requested autofill. 3901 * <li>If the view is part of a new partition. 3902 * </ol> 3903 * 3904 * @param id The id of the view that is entered. 3905 * @param viewState The view that is entered. 3906 * @param flags The flag that was passed by the AutofillManager. 3907 * 3908 * @return {@code true} if a new fill response is requested. 3909 */ 3910 @GuardedBy("mLock") requestNewFillResponseOnViewEnteredIfNecessaryLocked(@onNull AutofillId id, @NonNull ViewState viewState, int flags)3911 private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, 3912 @NonNull ViewState viewState, int flags) { 3913 // Force new response for manual request 3914 if ((flags & FLAG_MANUAL_REQUEST) != 0) { 3915 mSessionFlags.mAugmentedAutofillOnly = false; 3916 if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); 3917 requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags); 3918 return true; 3919 } 3920 3921 // If it's not, then check if it should start a partition. 3922 if (shouldStartNewPartitionLocked(id)) { 3923 if (sDebug) { 3924 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " 3925 + viewState.getStateAsString()); 3926 } 3927 // Fix to always let standard autofill start. 3928 // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as 3929 // augmentedOnly, but other fields are still fillable by standard autofill. 3930 mSessionFlags.mAugmentedAutofillOnly = false; 3931 requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags); 3932 return true; 3933 } 3934 3935 if (sVerbose) { 3936 Slog.v(TAG, "Not starting new partition for view " + id + ": " 3937 + viewState.getStateAsString()); 3938 } 3939 return false; 3940 } 3941 3942 /** 3943 * Determines if a new partition should be started for an id. 3944 * 3945 * @param id The id of the view that is entered 3946 * 3947 * @return {@code true} if a new partition should be started 3948 */ 3949 @GuardedBy("mLock") shouldStartNewPartitionLocked(@onNull AutofillId id)3950 private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { 3951 final ViewState currentView = mViewStates.get(id); 3952 if (mResponses == null) { 3953 return currentView != null && (currentView.getState() 3954 & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0; 3955 } 3956 3957 if (mSessionFlags.mExpiredResponse) { 3958 if (sDebug) { 3959 Slog.d(TAG, "Starting a new partition because the response has expired."); 3960 } 3961 return true; 3962 } 3963 3964 final int numResponses = mResponses.size(); 3965 if (numResponses >= AutofillManagerService.getPartitionMaxCount()) { 3966 Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id 3967 + " reached maximum of " + AutofillManagerService.getPartitionMaxCount()); 3968 return false; 3969 } 3970 3971 for (int responseNum = 0; responseNum < numResponses; responseNum++) { 3972 final FillResponse response = mResponses.valueAt(responseNum); 3973 3974 if (ArrayUtils.contains(response.getIgnoredIds(), id)) { 3975 return false; 3976 } 3977 3978 final SaveInfo saveInfo = response.getSaveInfo(); 3979 if (saveInfo != null) { 3980 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id) 3981 || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) { 3982 return false; 3983 } 3984 } 3985 3986 final List<Dataset> datasets = response.getDatasets(); 3987 if (datasets != null) { 3988 final int numDatasets = datasets.size(); 3989 3990 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) { 3991 final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds(); 3992 3993 if (fields != null && fields.contains(id)) { 3994 return false; 3995 } 3996 } 3997 } 3998 3999 if (ArrayUtils.contains(response.getAuthenticationIds(), id)) { 4000 return false; 4001 } 4002 } 4003 4004 return true; 4005 } 4006 4007 @GuardedBy("mLock") updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags)4008 void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, 4009 int flags) { 4010 if (mDestroyed) { 4011 Slog.w(TAG, "Call to Session#updateLocked() rejected - session: " 4012 + id + " destroyed"); 4013 return; 4014 } 4015 if (action == ACTION_RESPONSE_EXPIRED) { 4016 mSessionFlags.mExpiredResponse = true; 4017 if (sDebug) { 4018 Slog.d(TAG, "Set the response has expired."); 4019 } 4020 mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists( 4021 NOT_SHOWN_REASON_VIEW_CHANGED); 4022 mPresentationStatsEventLogger.logAndEndEvent(); 4023 return; 4024 } 4025 4026 id.setSessionId(this.id); 4027 if (sVerbose) { 4028 Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action=" 4029 + actionAsString(action) + ", flags=" + flags); 4030 } 4031 ViewState viewState = mViewStates.get(id); 4032 if (sVerbose) { 4033 Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId 4034 + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse 4035 + ", viewState=" + viewState); 4036 } 4037 4038 if (viewState == null) { 4039 if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED 4040 || action == ACTION_VIEW_ENTERED) { 4041 if (sVerbose) Slog.v(TAG, "Creating viewState for " + id); 4042 boolean isIgnored = isIgnoredLocked(id); 4043 viewState = new ViewState(id, this, 4044 isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL); 4045 mViewStates.put(id, viewState); 4046 4047 // TODO(b/73648631): for optimization purposes, should also ignore if change is 4048 // detectable, and batch-send them when the session is finished (but that will 4049 // require tracking detectable fields on AutofillManager) 4050 if (isIgnored) { 4051 if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + viewState); 4052 return; 4053 } 4054 } else { 4055 if (sVerbose) Slog.v(TAG, "Ignoring specific action when viewState=null"); 4056 return; 4057 } 4058 } 4059 4060 if ((flags & FLAG_RESET_FILL_DIALOG_STATE) != 0) { 4061 if (sDebug) Log.d(TAG, "force to reset fill dialog state"); 4062 mSessionFlags.mFillDialogDisabled = false; 4063 } 4064 4065 /* request assist structure for pcc */ 4066 if ((flags & FLAG_PCC_DETECTION) != 0) { 4067 requestAssistStructureForPccLocked(flags); 4068 return; 4069 } 4070 4071 if ((flags & FLAG_SCREEN_HAS_CREDMAN_FIELD) != 0) { 4072 mSessionFlags.mScreenHasCredmanField = true; 4073 } 4074 4075 switch(action) { 4076 case ACTION_START_SESSION: 4077 // View is triggering autofill. 4078 mCurrentViewId = viewState.id; 4079 viewState.update(value, virtualBounds, flags); 4080 startNewEventForPresentationStatsEventLogger(); 4081 mPresentationStatsEventLogger.maybeSetIsNewRequest(true); 4082 if (!isRequestSupportFillDialog(flags)) { 4083 mSessionFlags.mFillDialogDisabled = true; 4084 mPreviouslyFillDialogPotentiallyStarted = false; 4085 } else { 4086 // Set the default reason for now if the user doesn't trigger any focus event 4087 // on the autofillable view. This can be changed downstream when more 4088 // information is available or session is committed. 4089 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 4090 NOT_SHOWN_REASON_NO_FOCUS); 4091 mPreviouslyFillDialogPotentiallyStarted = true; 4092 } 4093 requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); 4094 break; 4095 case ACTION_VALUE_CHANGED: 4096 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { 4097 // Must cancel the session if the value of the URL bar changed 4098 final String currentUrl = mUrlBar == null ? null 4099 : mUrlBar.getText().toString().trim(); 4100 if (currentUrl == null) { 4101 // Validation check - shouldn't happen. 4102 wtf(null, "URL bar value changed, but current value is null"); 4103 return; 4104 } 4105 if (value == null || ! value.isText()) { 4106 // Validation check - shouldn't happen. 4107 wtf(null, "URL bar value changed to null or non-text: %s", value); 4108 return; 4109 } 4110 final String newUrl = value.getTextValue().toString(); 4111 if (newUrl.equals(currentUrl)) { 4112 if (sDebug) Slog.d(TAG, "Ignoring change on URL bar as it's the same"); 4113 return; 4114 } 4115 if (mSaveOnAllViewsInvisible) { 4116 // We cannot cancel the session because it could hinder Save when all views 4117 // are finished, as the URL bar changed callback is usually called before 4118 // the virtual views become invisible. 4119 if (sDebug) { 4120 Slog.d(TAG, "Ignoring change on URL because session will finish when " 4121 + "views are gone"); 4122 } 4123 return; 4124 } 4125 if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed"); 4126 forceRemoveFromServiceLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE); 4127 return; 4128 } 4129 if (!Objects.equals(value, viewState.getCurrentValue())) { 4130 logIfViewClearedLocked(id, value, viewState); 4131 updateViewStateAndUiOnValueChangedLocked(id, value, viewState, flags); 4132 } 4133 break; 4134 case ACTION_VIEW_ENTERED: 4135 mLatencyBaseTime = SystemClock.elapsedRealtime(); 4136 boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted; 4137 mPreviouslyFillDialogPotentiallyStarted = false; 4138 if (sVerbose && virtualBounds != null) { 4139 Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds); 4140 } 4141 4142 final boolean isSameViewEntered = Objects.equals(mCurrentViewId, viewState.id); 4143 // Update the view states first... 4144 mCurrentViewId = viewState.id; 4145 if (value != null) { 4146 viewState.setCurrentValue(value); 4147 } 4148 4149 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { 4150 if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")"); 4151 return; 4152 } 4153 4154 synchronized (mLock) { 4155 if (!mLogViewEntered) { 4156 // If the current request is for FillDialog (preemptive) 4157 // then this is the first time that the view is entered 4158 // (mLogViewEntered == false) in this case, setLastResponse() 4159 // has already been called, so just log here. 4160 // If the current request is not and (mLogViewEntered == false) 4161 // then the last session is being tracked (setLastResponse not called) 4162 // so this calling logViewEntered will be a nop. 4163 // Calling logViewEntered() twice will only log it once 4164 // TODO(271181979): this is broken for multiple partitions 4165 mService.logViewEntered(this.id, null); 4166 } 4167 4168 // If this is the first time view is entered for inline, the last 4169 // session is still being tracked, so logViewEntered() needs 4170 // to be delayed until setLastResponse is called. 4171 // For fill dialog requests case logViewEntered is already called above 4172 // so this will do nothing. Assumption: only one fill dialog per session 4173 mLogViewEntered = true; 4174 } 4175 4176 // Previously, fill request will only start whenever a view is entered. 4177 // With Fill Dialog, request starts prior to view getting entered. So, we can't end 4178 // the event at this moment, otherwise we will be wrongly attributing fill dialog 4179 // event as concluded. 4180 if (!wasPreviouslyFillDialog) { 4181 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 4182 NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); 4183 mPresentationStatsEventLogger.logAndEndEvent(); 4184 } 4185 4186 if ((flags & FLAG_MANUAL_REQUEST) == 0) { 4187 // Not a manual request 4188 if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains( 4189 id)) { 4190 // Regular autofill handled the view and returned null response, but it 4191 // triggered augmented autofill 4192 if (!isSameViewEntered) { 4193 if (sDebug) Slog.d(TAG, "trigger augmented autofill."); 4194 triggerAugmentedAutofillLocked(flags); 4195 } else { 4196 if (sDebug) { 4197 Slog.d(TAG, "skip augmented autofill for same view: " 4198 + "same view entered"); 4199 } 4200 } 4201 return; 4202 } else if (mSessionFlags.mAugmentedAutofillOnly && isSameViewEntered) { 4203 // Regular autofill is disabled. 4204 if (sDebug) { 4205 Slog.d(TAG, "skip augmented autofill for same view: " 4206 + "standard autofill disabled."); 4207 } 4208 return; 4209 } 4210 } 4211 // If previous request was FillDialog request, a logger event was already started 4212 if (!wasPreviouslyFillDialog) { 4213 startNewEventForPresentationStatsEventLogger(); 4214 } 4215 if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) { 4216 // If a new request was issued even if previously it was fill dialog request, 4217 // we should end the log event, and start a new one. However, it leaves us 4218 // susceptible to race condition. But since mPresentationStatsEventLogger is 4219 // lock guarded, we should be safe. 4220 if (wasPreviouslyFillDialog) { 4221 mPresentationStatsEventLogger.logAndEndEvent(); 4222 startNewEventForPresentationStatsEventLogger(); 4223 } 4224 return; 4225 } 4226 4227 if (viewState.getResponse() != null) { 4228 FillResponse response = viewState.getResponse(); 4229 mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId()); 4230 mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( 4231 mFieldClassificationIdSnapshot); 4232 mPresentationStatsEventLogger.maybeSetAvailableCount( 4233 response.getDatasets(), mCurrentViewId); 4234 } 4235 4236 if (isSameViewEntered) { 4237 setFillDialogDisabledAndStartInput(); 4238 return; 4239 } 4240 4241 // If the ViewState is ready to be displayed, onReady() will be called. 4242 viewState.update(value, virtualBounds, flags); 4243 break; 4244 case ACTION_VIEW_EXITED: 4245 if (Objects.equals(mCurrentViewId, viewState.id)) { 4246 if (sVerbose) Slog.v(TAG, "Exiting view " + id); 4247 mUi.hideFillUi(this); 4248 mUi.hideFillDialog(this); 4249 hideAugmentedAutofillLocked(viewState); 4250 // We don't send an empty response to IME so that it doesn't cause UI flicker 4251 // on the IME side if it arrives before the input view is finished on the IME. 4252 mInlineSessionController.resetInlineFillUiLocked(); 4253 mCurrentViewId = null; 4254 4255 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 4256 NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); 4257 mPresentationStatsEventLogger.logAndEndEvent(); 4258 } 4259 break; 4260 default: 4261 Slog.w(TAG, "updateLocked(): unknown action: " + action); 4262 } 4263 } 4264 4265 @GuardedBy("mLock") hideAugmentedAutofillLocked(@onNull ViewState viewState)4266 private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) { 4267 if ((viewState.getState() 4268 & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { 4269 viewState.resetState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL); 4270 cancelAugmentedAutofillLocked(); 4271 } 4272 } 4273 4274 /** 4275 * Checks whether a view should be ignored. 4276 */ 4277 @GuardedBy("mLock") isIgnoredLocked(AutofillId id)4278 private boolean isIgnoredLocked(AutofillId id) { 4279 // Always check the latest response only 4280 final FillResponse response = getLastResponseLocked(null); 4281 if (response == null) return false; 4282 4283 return ArrayUtils.contains(response.getIgnoredIds(), id); 4284 } 4285 4286 @GuardedBy("mLock") logIfViewClearedLocked(AutofillId id, AutofillValue value, ViewState viewState)4287 private void logIfViewClearedLocked(AutofillId id, AutofillValue value, ViewState viewState) { 4288 if ((value == null || value.isEmpty()) 4289 && viewState.getCurrentValue() != null 4290 && viewState.getCurrentValue().isText() 4291 && viewState.getCurrentValue().getTextValue() != null 4292 && getSaveInfoLocked() != null) { 4293 final int length = viewState.getCurrentValue().getTextValue().length(); 4294 if (sDebug) { 4295 Slog.d(TAG, "updateLocked(" + id + "): resetting value that was " 4296 + length + " chars long"); 4297 } 4298 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) 4299 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); 4300 mMetricsLogger.write(log); 4301 } 4302 } 4303 4304 @GuardedBy("mLock") updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, ViewState viewState, int flags)4305 private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, 4306 ViewState viewState, int flags) { 4307 final String textValue; 4308 if (value == null || !value.isText()) { 4309 textValue = null; 4310 } else { 4311 final CharSequence text = value.getTextValue(); 4312 // Text should never be null, but it doesn't hurt to check to avoid a 4313 // system crash... 4314 textValue = (text == null) ? null : text.toString(); 4315 } 4316 updateFilteringStateOnValueChangedLocked(textValue, viewState); 4317 4318 viewState.setCurrentValue(value); 4319 4320 final String filterText = textValue; 4321 4322 final AutofillValue filledValue = viewState.getAutofilledValue(); 4323 if (filledValue != null) { 4324 if (filledValue.equals(value)) { 4325 // When the update is caused by autofilling the view, just update the 4326 // value, not the UI. 4327 if (sVerbose) { 4328 Slog.v(TAG, "ignoring autofilled change on id " + id); 4329 } 4330 // TODO(b/156099633): remove this once framework gets out of business of resending 4331 // inline suggestions when IME visibility changes. 4332 mInlineSessionController.hideInlineSuggestionsUiLocked(viewState.id); 4333 viewState.resetState(ViewState.STATE_CHANGED); 4334 return; 4335 } else if ((viewState.id.equals(this.mCurrentViewId)) 4336 && (viewState.getState() & ViewState.STATE_AUTOFILLED) != 0) { 4337 // Remove autofilled state once field is changed after autofilling. 4338 if (sVerbose) { 4339 Slog.v(TAG, "field changed after autofill on id " + id); 4340 } 4341 viewState.resetState(ViewState.STATE_AUTOFILLED); 4342 final ViewState currentView = mViewStates.get(mCurrentViewId); 4343 currentView.maybeCallOnFillReady(flags); 4344 } 4345 } 4346 4347 if (viewState.id.equals(this.mCurrentViewId) 4348 && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) { 4349 if ((viewState.getState() & ViewState.STATE_INLINE_DISABLED) != 0) { 4350 mInlineSessionController.disableFilterMatching(viewState.id); 4351 } 4352 mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText); 4353 } else if (viewState.id.equals(this.mCurrentViewId) 4354 && (viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { 4355 if (!TextUtils.isEmpty(filterText)) { 4356 // TODO: we should be able to replace this with controller#filterInlineFillUiLocked 4357 // to accomplish filtering for augmented autofill. 4358 mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); 4359 } 4360 } 4361 4362 viewState.setState(ViewState.STATE_CHANGED); 4363 getUiForShowing().filterFillUi(filterText, this); 4364 } 4365 4366 /** 4367 * Disable filtering of inline suggestions for further text changes in this view if any 4368 * character was removed earlier and now any character is being added. Such behaviour may 4369 * indicate the IME attempting to probe the potentially sensitive content of inline suggestions. 4370 */ 4371 @GuardedBy("mLock") updateFilteringStateOnValueChangedLocked(@ullable String newTextValue, ViewState viewState)4372 private void updateFilteringStateOnValueChangedLocked(@Nullable String newTextValue, 4373 ViewState viewState) { 4374 if (newTextValue == null) { 4375 // Don't just return here, otherwise the IME can circumvent this logic using non-text 4376 // values. 4377 newTextValue = ""; 4378 } 4379 final AutofillValue currentValue = viewState.getCurrentValue(); 4380 final String currentTextValue; 4381 if (currentValue == null || !currentValue.isText()) { 4382 currentTextValue = ""; 4383 } else { 4384 currentTextValue = currentValue.getTextValue().toString(); 4385 } 4386 4387 if ((viewState.getState() & ViewState.STATE_CHAR_REMOVED) == 0) { 4388 if (!containsCharsInOrder(newTextValue, currentTextValue)) { 4389 viewState.setState(ViewState.STATE_CHAR_REMOVED); 4390 } 4391 } else if (!containsCharsInOrder(currentTextValue, newTextValue)) { 4392 // Characters were added or replaced. 4393 viewState.setState(ViewState.STATE_INLINE_DISABLED); 4394 } 4395 } 4396 4397 @Override onFillReady(@onNull FillResponse response, @NonNull AutofillId filledId, @Nullable AutofillValue value, int flags)4398 public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId, 4399 @Nullable AutofillValue value, int flags) { 4400 synchronized (mLock) { 4401 mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( 4402 mFieldClassificationIdSnapshot); 4403 if (mDestroyed) { 4404 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: " 4405 + id + " destroyed"); 4406 mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED); 4407 mSaveEventLogger.logAndEndEvent(); 4408 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( 4409 NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); 4410 mPresentationStatsEventLogger.logAndEndEvent(); 4411 return; 4412 } 4413 } 4414 4415 String filterText = null; 4416 if (value != null && value.isText()) { 4417 filterText = value.getTextValue().toString(); 4418 } 4419 4420 final CharSequence serviceLabel; 4421 final Drawable serviceIcon; 4422 synchronized (this.mService.mLock) { 4423 serviceLabel = mService.getServiceLabelLocked(); 4424 serviceIcon = mService.getServiceIconLocked(); 4425 } 4426 if (serviceLabel == null || serviceIcon == null) { 4427 wtf(null, "onFillReady(): no service label or icon"); 4428 return; 4429 } 4430 4431 synchronized (mLock) { 4432 // Time passed since Session was created 4433 final long suggestionSentRelativeTimestamp = 4434 SystemClock.elapsedRealtime() - mLatencyBaseTime; 4435 mPresentationStatsEventLogger.maybeSetSuggestionSentTimestampMs( 4436 (int) (suggestionSentRelativeTimestamp)); 4437 } 4438 4439 final AutofillId[] ids = response.getFillDialogTriggerIds(); 4440 if (ids != null && ArrayUtils.contains(ids, filledId)) { 4441 if (requestShowFillDialog(response, filledId, filterText, flags)) { 4442 synchronized (mLock) { 4443 final ViewState currentView = mViewStates.get(mCurrentViewId); 4444 currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); 4445 mPresentationStatsEventLogger.maybeSetCountShown( 4446 response.getDatasets(), mCurrentViewId); 4447 mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG); 4448 } 4449 // Just show fill dialog once, so disabled after shown. 4450 // Note: Cannot disable before requestShowFillDialog() because the method 4451 // need to check whether fill dialog enabled. 4452 setFillDialogDisabled(); 4453 synchronized (mLock) { 4454 // Logs when fill dialog ui is shown; time since Session was created 4455 final long fillDialogUiShownRelativeTimestamp = 4456 SystemClock.elapsedRealtime() - mLatencyBaseTime; 4457 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs( 4458 (int) (fillDialogUiShownRelativeTimestamp)); 4459 } 4460 return; 4461 } else { 4462 setFillDialogDisabled(); 4463 } 4464 4465 } 4466 4467 if (response.supportsInlineSuggestions()) { 4468 synchronized (mLock) { 4469 if (requestShowInlineSuggestionsLocked(response, filterText)) { 4470 // Cannot tell for sure that InlineSuggestions are shown yet, IME needs to send 4471 // back a response via callback. 4472 final ViewState currentView = mViewStates.get(mCurrentViewId); 4473 currentView.setState(ViewState.STATE_INLINE_SHOWN); 4474 // TODO(b/234475358): Log more accurate value of number of inline suggestions 4475 // shown, inflated, and filtered. 4476 mPresentationStatsEventLogger.maybeSetCountShown( 4477 response.getDatasets(), mCurrentViewId); 4478 mPresentationStatsEventLogger.maybeSetInlinePresentationAndSuggestionHostUid( 4479 mContext, userId); 4480 return; 4481 } 4482 } 4483 } 4484 4485 getUiForShowing().showFillUi(filledId, response, filterText, 4486 mService.getServicePackageName(), mComponentName, 4487 serviceLabel, serviceIcon, this, mContext, id, mCompatMode); 4488 4489 synchronized (mLock) { 4490 mPresentationStatsEventLogger.maybeSetCountShown( 4491 response.getDatasets(), mCurrentViewId); 4492 mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU); 4493 } 4494 4495 synchronized (mLock) { 4496 if (mUiShownTime == 0) { 4497 // Log first time UI is shown. 4498 mUiShownTime = SystemClock.elapsedRealtime(); 4499 final long duration = mUiShownTime - mStartTime; 4500 // This logs when dropdown ui was shown. Timestamp is relative to 4501 // when the session was created 4502 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs( 4503 (int) (mUiShownTime - mLatencyBaseTime)); 4504 4505 if (sDebug) { 4506 final StringBuilder msg = new StringBuilder("1st UI for ") 4507 .append(mActivityToken) 4508 .append(" shown in "); 4509 TimeUtils.formatDuration(duration, msg); 4510 Slog.d(TAG, msg.toString()); 4511 } 4512 final StringBuilder historyLog = new StringBuilder("id=").append(id) 4513 .append(" app=").append(mActivityToken) 4514 .append(" svc=").append(mService.getServicePackageName()) 4515 .append(" latency="); 4516 TimeUtils.formatDuration(duration, historyLog); 4517 mUiLatencyHistory.log(historyLog.toString()); 4518 4519 addTaggedDataToRequestLogLocked(response.getRequestId(), 4520 MetricsEvent.FIELD_AUTOFILL_DURATION, duration); 4521 } 4522 } 4523 } 4524 4525 @GuardedBy("mLock") updateFillDialogTriggerIdsLocked()4526 private void updateFillDialogTriggerIdsLocked() { 4527 final FillResponse response = getLastResponseLocked(null); 4528 4529 if (response == null) return; 4530 4531 final AutofillId[] ids = response.getFillDialogTriggerIds(); 4532 notifyClientFillDialogTriggerIds(ids == null ? null : Arrays.asList(ids)); 4533 } 4534 notifyClientFillDialogTriggerIds(List<AutofillId> fieldIds)4535 private void notifyClientFillDialogTriggerIds(List<AutofillId> fieldIds) { 4536 try { 4537 if (sVerbose) { 4538 Slog.v(TAG, "notifyFillDialogTriggerIds(): " + fieldIds); 4539 } 4540 getClient().notifyFillDialogTriggerIds(fieldIds); 4541 } catch (RemoteException e) { 4542 Slog.w(TAG, "Cannot set trigger ids for fill dialog", e); 4543 } 4544 } 4545 isFillDialogUiEnabled()4546 private boolean isFillDialogUiEnabled() { 4547 synchronized (mLock) { 4548 return !mSessionFlags.mFillDialogDisabled && !mSessionFlags.mScreenHasCredmanField; 4549 } 4550 } 4551 setFillDialogDisabled()4552 private void setFillDialogDisabled() { 4553 synchronized (mLock) { 4554 mSessionFlags.mFillDialogDisabled = true; 4555 } 4556 notifyClientFillDialogTriggerIds(null); 4557 } 4558 setFillDialogDisabledAndStartInput()4559 private void setFillDialogDisabledAndStartInput() { 4560 if (getUiForShowing().isFillDialogShowing()) { 4561 setFillDialogDisabled(); 4562 final AutofillId id; 4563 synchronized (mLock) { 4564 id = mCurrentViewId; 4565 } 4566 requestShowSoftInput(id); 4567 } 4568 } 4569 requestShowFillDialog(FillResponse response, AutofillId filledId, String filterText, int flags)4570 private boolean requestShowFillDialog(FillResponse response, 4571 AutofillId filledId, String filterText, int flags) { 4572 if (!isFillDialogUiEnabled()) { 4573 // Unsupported fill dialog UI 4574 if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled"); 4575 return false; 4576 } 4577 4578 if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) { 4579 // IME is showing, fallback to normal suggestions UI 4580 if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing"); 4581 return false; 4582 } 4583 4584 if (mInlineSessionController.isImeShowing()) { 4585 // IME is showing, fallback to normal suggestions UI 4586 // Note: only work when inline suggestions supported 4587 return false; 4588 } 4589 4590 synchronized (mLock) { 4591 if (mLastFillDialogTriggerIds == null 4592 || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) { 4593 // Last fill dialog triggered ids are changed. 4594 if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); 4595 return false; 4596 } 4597 4598 } 4599 4600 Drawable serviceIcon = null; 4601 synchronized (mLock) { 4602 serviceIcon = getServiceIcon(response); 4603 } 4604 4605 getUiForShowing().showFillDialog(filledId, response, filterText, 4606 mService.getServicePackageName(), mComponentName, serviceIcon, this, 4607 id, mCompatMode, mPresentationStatsEventLogger); 4608 return true; 4609 } 4610 4611 /** 4612 * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able 4613 * to be fetched, use the default provider icon instead 4614 * 4615 * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise 4616 */ 4617 @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's 4618 // actually the same object as mLock. 4619 // TODO: Expose mService.mLock or redesign instead. 4620 @GuardedBy("mLock") getServiceIcon(FillResponse response)4621 private Drawable getServiceIcon(FillResponse response) { 4622 Drawable serviceIcon = null; 4623 // Try to get the custom Icon, if one was passed through FillResponse 4624 int iconResourceId = response.getIconResourceId(); 4625 if (iconResourceId != 0) { 4626 serviceIcon = mService.getMaster().getContext().getPackageManager() 4627 .getDrawable( 4628 mService.getServicePackageName(), 4629 iconResourceId, 4630 null); 4631 } 4632 4633 // Custom icon wasn't fetched, use the default package icon instead 4634 if (serviceIcon == null) { 4635 serviceIcon = mService.getServiceIconLocked(); 4636 } 4637 4638 return serviceIcon; 4639 } 4640 4641 /** 4642 * Get the custom label that was passed through FillResponse. If the custom label 4643 * wasn't able to be fetched, use the default provider icon instead 4644 * 4645 * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise 4646 */ 4647 @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's 4648 // actually the same object as mLock. 4649 // TODO: Expose mService.mLock or redesign instead. 4650 @GuardedBy("mLock") getServiceLabel(FillResponse response)4651 private CharSequence getServiceLabel(FillResponse response) { 4652 CharSequence serviceLabel = null; 4653 // Try to get the custom Service name, if one was passed through FillResponse 4654 int customServiceNameId = response.getServiceDisplayNameResourceId(); 4655 if (customServiceNameId != 0) { 4656 serviceLabel = mService.getMaster().getContext().getPackageManager() 4657 .getText( 4658 mService.getServicePackageName(), 4659 customServiceNameId, 4660 null); 4661 } 4662 4663 // Custom label wasn't fetched, use the default package name instead 4664 if (serviceLabel == null) { 4665 serviceLabel = mService.getServiceLabelLocked(); 4666 } 4667 4668 return serviceLabel; 4669 } 4670 4671 /** 4672 * Returns whether we made a request to show inline suggestions. 4673 */ requestShowInlineSuggestionsLocked(@onNull FillResponse response, @Nullable String filterText)4674 private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response, 4675 @Nullable String filterText) { 4676 if (mCurrentViewId == null) { 4677 Log.w(TAG, "requestShowInlineSuggestionsLocked(): no view currently focused"); 4678 return false; 4679 } 4680 final AutofillId focusedId = mCurrentViewId; 4681 4682 final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest = 4683 mInlineSessionController.getInlineSuggestionsRequestLocked(); 4684 if (!inlineSuggestionsRequest.isPresent()) { 4685 Log.w(TAG, "InlineSuggestionsRequest unavailable"); 4686 return false; 4687 } 4688 4689 final RemoteInlineSuggestionRenderService remoteRenderService = 4690 mService.getRemoteInlineSuggestionRenderServiceLocked(); 4691 if (remoteRenderService == null) { 4692 Log.w(TAG, "RemoteInlineSuggestionRenderService not found"); 4693 return false; 4694 } 4695 4696 // Set this to false - we are requesting a new inline request and haven't shown 4697 // anything yet 4698 synchronized (mLock) { 4699 mLoggedInlineDatasetShown = false; 4700 } 4701 4702 final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = 4703 new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId, 4704 filterText, remoteRenderService, userId, id); 4705 InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response, 4706 new InlineFillUi.InlineSuggestionUiCallback() { 4707 @Override 4708 public void autofill(@NonNull Dataset dataset, int datasetIndex) { 4709 fill(response.getRequestId(), datasetIndex, dataset, UI_TYPE_INLINE); 4710 } 4711 4712 @Override 4713 public void authenticate(int requestId, int datasetIndex) { 4714 Session.this.authenticate(response.getRequestId(), datasetIndex, 4715 response.getAuthentication(), response.getClientState(), 4716 UI_TYPE_INLINE); 4717 } 4718 4719 @Override 4720 public void startIntentSender(@NonNull IntentSender intentSender) { 4721 Session.this.startIntentSender(intentSender, new Intent()); 4722 } 4723 4724 @Override 4725 public void onError() { 4726 synchronized (mLock) { 4727 mInlineSessionController.setInlineFillUiLocked( 4728 InlineFillUi.emptyUi(focusedId)); 4729 } 4730 } 4731 4732 @Override 4733 public void onInflate() { 4734 Session.this.onShown(UI_TYPE_INLINE); 4735 } 4736 }); 4737 return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); 4738 } 4739 isDestroyed()4740 boolean isDestroyed() { 4741 synchronized (mLock) { 4742 return mDestroyed; 4743 } 4744 } 4745 getClient()4746 IAutoFillManagerClient getClient() { 4747 synchronized (mLock) { 4748 return mClient; 4749 } 4750 } 4751 notifyUnavailableToClient(int sessionFinishedState, @Nullable ArrayList<AutofillId> autofillableIds)4752 private void notifyUnavailableToClient(int sessionFinishedState, 4753 @Nullable ArrayList<AutofillId> autofillableIds) { 4754 synchronized (mLock) { 4755 if (mCurrentViewId == null) return; 4756 try { 4757 if (mHasCallback) { 4758 mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState); 4759 } else if (sessionFinishedState != AutofillManager.STATE_UNKNOWN) { 4760 mClient.setSessionFinished(sessionFinishedState, autofillableIds); 4761 } 4762 } catch (RemoteException e) { 4763 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e); 4764 } 4765 } 4766 } 4767 notifyDisableAutofillToClient(long disableDuration, ComponentName componentName)4768 private void notifyDisableAutofillToClient(long disableDuration, ComponentName componentName) { 4769 synchronized (mLock) { 4770 if (mCurrentViewId == null) return; 4771 try { 4772 mClient.notifyDisableAutofill(disableDuration, componentName); 4773 } catch (RemoteException e) { 4774 Slog.e(TAG, "Error notifying client disable autofill: id=" + mCurrentViewId, e); 4775 } 4776 } 4777 } 4778 4779 @GuardedBy("mLock") updateTrackedIdsLocked()4780 private void updateTrackedIdsLocked() { 4781 // Only track the views of the last response as only those are reported back to the 4782 // service, see #showSaveLocked 4783 final FillResponse response = getLastResponseLocked(null); 4784 if (response == null) return; 4785 4786 ArraySet<AutofillId> trackedViews = null; 4787 mSaveOnAllViewsInvisible = false; 4788 boolean saveOnFinish = true; 4789 final SaveInfo saveInfo = response.getSaveInfo(); 4790 final AutofillId saveTriggerId; 4791 final int flags; 4792 if (saveInfo != null) { 4793 saveTriggerId = saveInfo.getTriggerId(); 4794 if (saveTriggerId != null) { 4795 writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION); 4796 mSaveEventLogger.maybeSetSaveUiShownReason(SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET); 4797 } 4798 flags = saveInfo.getFlags(); 4799 mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; 4800 4801 mFillResponseEventLogger.maybeSetSaveUiTriggerIds(HAVE_SAVE_TRIGGER_ID); 4802 4803 // Start to log Save event. 4804 mSaveEventLogger.maybeSetRequestId(response.getRequestId()); 4805 mSaveEventLogger.maybeSetAppPackageUid(uid); 4806 mSaveEventLogger.maybeSetSaveUiTriggerIds(HAVE_SAVE_TRIGGER_ID); 4807 mSaveEventLogger.maybeSetFlag(flags); 4808 4809 // We only need to track views if we want to save once they become invisible. 4810 if (mSaveOnAllViewsInvisible) { 4811 if (trackedViews == null) { 4812 trackedViews = new ArraySet<>(); 4813 } 4814 if (saveInfo.getRequiredIds() != null) { 4815 Collections.addAll(trackedViews, saveInfo.getRequiredIds()); 4816 mSaveEventLogger.maybeSetSaveUiShownReason( 4817 SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE); 4818 } 4819 4820 if (saveInfo.getOptionalIds() != null) { 4821 Collections.addAll(trackedViews, saveInfo.getOptionalIds()); 4822 mSaveEventLogger.maybeSetSaveUiShownReason( 4823 SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE); 4824 } 4825 } 4826 if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { 4827 mSaveEventLogger.maybeSetSaveUiShownReason( 4828 SAVE_UI_SHOWN_REASON_UNKNOWN); 4829 mSaveEventLogger.maybeSetSaveUiNotShownReason( 4830 NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG); 4831 saveOnFinish = false; 4832 } 4833 4834 } else { 4835 flags = 0; 4836 mSaveEventLogger.maybeSetSaveUiNotShownReason( 4837 NO_SAVE_REASON_NO_SAVE_INFO); 4838 saveTriggerId = null; 4839 } 4840 4841 // Must also track that are part of datasets, otherwise the FillUI won't be hidden when 4842 // they go away (if they're not savable). 4843 4844 final List<Dataset> datasets = response.getDatasets(); 4845 ArraySet<AutofillId> fillableIds = null; 4846 if (datasets != null) { 4847 for (int i = 0; i < datasets.size(); i++) { 4848 final Dataset dataset = datasets.get(i); 4849 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds(); 4850 if (fieldIds == null) continue; 4851 4852 for (int j = 0; j < fieldIds.size(); j++) { 4853 final AutofillId id = fieldIds.get(j); 4854 if (id != null) { 4855 if (trackedViews == null || !trackedViews.contains(id)) { 4856 fillableIds = ArrayUtils.add(fillableIds, id); 4857 } 4858 } 4859 } 4860 } 4861 } 4862 4863 try { 4864 if (sVerbose) { 4865 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds 4866 + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish 4867 + " flags: " + flags + " hasSaveInfo: " + (saveInfo != null)); 4868 } 4869 mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, 4870 saveOnFinish, toArray(fillableIds), saveTriggerId); 4871 } catch (RemoteException e) { 4872 Slog.w(TAG, "Cannot set tracked ids", e); 4873 } 4874 } 4875 4876 /** 4877 * Sets the state of views that failed to autofill. 4878 */ 4879 @GuardedBy("mLock") setAutofillFailureLocked(@onNull List<AutofillId> ids)4880 void setAutofillFailureLocked(@NonNull List<AutofillId> ids) { 4881 for (int i = 0; i < ids.size(); i++) { 4882 final AutofillId id = ids.get(i); 4883 final ViewState viewState = mViewStates.get(id); 4884 if (viewState == null) { 4885 Slog.w(TAG, "setAutofillFailure(): no view for id " + id); 4886 continue; 4887 } 4888 viewState.resetState(ViewState.STATE_AUTOFILLED); 4889 final int state = viewState.getState(); 4890 viewState.setState(state | ViewState.STATE_AUTOFILL_FAILED); 4891 if (sVerbose) { 4892 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString()); 4893 } 4894 } 4895 } 4896 4897 @GuardedBy("mLock") replaceResponseLocked(@onNull FillResponse oldResponse, @NonNull FillResponse newResponse, @Nullable Bundle newClientState)4898 private void replaceResponseLocked(@NonNull FillResponse oldResponse, 4899 @NonNull FillResponse newResponse, @Nullable Bundle newClientState) { 4900 // Disassociate view states with the old response 4901 setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true); 4902 // Move over the id 4903 newResponse.setRequestId(oldResponse.getRequestId()); 4904 // Now process the new response 4905 processResponseLockedForPcc(newResponse, newClientState, 0); 4906 } 4907 4908 @GuardedBy("mLock") processNullResponseLocked(int requestId, int flags)4909 private void processNullResponseLocked(int requestId, int flags) { 4910 unregisterDelayedFillBroadcastLocked(); 4911 if ((flags & FLAG_MANUAL_REQUEST) != 0) { 4912 getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this); 4913 } 4914 4915 final FillContext context = getFillContextByRequestIdLocked(requestId); 4916 4917 final ArrayList<AutofillId> autofillableIds; 4918 if (context != null) { 4919 final AssistStructure structure = context.getStructure(); 4920 autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */true); 4921 } else { 4922 Slog.w(TAG, "processNullResponseLocked(): no context for req " + requestId); 4923 autofillableIds = null; 4924 } 4925 // Log the existing FillResponse event. 4926 mFillResponseEventLogger.maybeSetAvailableCount(0); 4927 mFillResponseEventLogger.logAndEndEvent(); 4928 mService.resetLastResponse(); 4929 4930 // The default autofill service cannot fulfill the request, let's check if the augmented 4931 // autofill service can. 4932 mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags); 4933 if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) { 4934 if (sVerbose) { 4935 Slog.v(TAG, "canceling session " + id + " when service returned null and it cannot " 4936 + "be augmented. AutofillableIds: " + autofillableIds); 4937 } 4938 // Nothing to be done, but need to notify client. 4939 notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds); 4940 removeFromService(); 4941 } else { 4942 if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { 4943 if (sVerbose) { 4944 Slog.v(TAG, "keeping session " + id + " when service returned null and " 4945 + "augmented service is disabled for password fields. " 4946 + "AutofillableIds: " + autofillableIds); 4947 } 4948 mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); 4949 } else { 4950 if (sVerbose) { 4951 Slog.v(TAG, "keeping session " + id + " when service returned null but " 4952 + "it can be augmented. AutofillableIds: " + autofillableIds); 4953 } 4954 } 4955 mAugmentedAutofillableIds = autofillableIds; 4956 try { 4957 mClient.setState(AutofillManager.SET_STATE_FLAG_FOR_AUTOFILL_ONLY); 4958 } catch (RemoteException e) { 4959 Slog.e(TAG, "Error setting client to autofill-only", e); 4960 } 4961 } 4962 } 4963 4964 /** 4965 * Tries to trigger Augmented Autofill when the standard service could not fulfill a request. 4966 * 4967 * <p> The request may not have been sent when this method returns as it may be waiting for 4968 * the inline suggestion request asynchronously. 4969 * 4970 * @return callback to destroy the autofill UI, or {@code null} if not supported. 4971 */ 4972 // TODO(b/123099468): might need to call it in other places, like when the service returns a 4973 // non-null response but without datasets (for example, just SaveInfo) 4974 @GuardedBy("mLock") triggerAugmentedAutofillLocked(int flags)4975 private Runnable triggerAugmentedAutofillLocked(int flags) { 4976 // TODO: (b/141703197) Fix later by passing info to service. 4977 if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { 4978 return null; 4979 } 4980 4981 // Check if Smart Suggestions is supported... 4982 final @SmartSuggestionMode int supportedModes = mService 4983 .getSupportedSmartSuggestionModesLocked(); 4984 if (supportedModes == 0) { 4985 if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no supported modes"); 4986 return null; 4987 } 4988 4989 // ...then if the service is set for the user 4990 4991 final RemoteAugmentedAutofillService remoteService = mService 4992 .getRemoteAugmentedAutofillServiceLocked(); 4993 if (remoteService == null) { 4994 if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user"); 4995 return null; 4996 } 4997 4998 // Define which mode will be used 4999 final int mode; 5000 if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) { 5001 mode = FLAG_SMART_SUGGESTION_SYSTEM; 5002 } else { 5003 Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes); 5004 return null; 5005 } 5006 5007 if (mCurrentViewId == null) { 5008 Slog.w(TAG, "triggerAugmentedAutofillLocked(): no view currently focused"); 5009 return null; 5010 } 5011 5012 final boolean isAllowlisted = mService 5013 .isWhitelistedForAugmentedAutofillLocked(mComponentName); 5014 5015 if (!isAllowlisted) { 5016 if (sVerbose) { 5017 Slog.v(TAG, "triggerAugmentedAutofillLocked(): " 5018 + ComponentName.flattenToShortString(mComponentName) + " not whitelisted "); 5019 } 5020 logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), 5021 mCurrentViewId, isAllowlisted, /* isInline= */ null); 5022 return null; 5023 } 5024 5025 if (sVerbose) { 5026 Slog.v(TAG, "calling Augmented Autofill Service (" 5027 + ComponentName.flattenToShortString(remoteService.getComponentName()) 5028 + ") on view " + mCurrentViewId + " using suggestion mode " 5029 + getSmartSuggestionModeToString(mode) 5030 + " when server returned null for session " + this.id); 5031 } 5032 // Log FillRequest for Augmented Autofill. 5033 mFillRequestEventLogger.startLogForNewRequest(); 5034 mRequestCount++; 5035 mFillRequestEventLogger.maybeSetAppPackageUid(uid); 5036 mFillRequestEventLogger.maybeSetFlags(mFlags); 5037 mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID); 5038 mFillRequestEventLogger.maybeSetIsAugmented(true); 5039 mFillRequestEventLogger.logAndEndEvent(); 5040 5041 final ViewState viewState = mViewStates.get(mCurrentViewId); 5042 viewState.setState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL); 5043 final AutofillValue currentValue = viewState.getCurrentValue(); 5044 5045 if (mAugmentedRequestsLogs == null) { 5046 mAugmentedRequestsLogs = new ArrayList<>(); 5047 } 5048 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST, 5049 remoteService.getComponentName().getPackageName()); 5050 mAugmentedRequestsLogs.add(log); 5051 5052 final AutofillId focusedId = mCurrentViewId; 5053 5054 final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill = 5055 new AugmentedAutofillInlineSuggestionRequestConsumer( 5056 this, focusedId, isAllowlisted, mode, currentValue); 5057 5058 // When the inline suggestion render service is available and the view is focused, there 5059 // are 3 cases when augmented autofill should ask IME for inline suggestion request, 5060 // because standard autofill flow didn't: 5061 // 1. the field is augmented autofill only (when standard autofill provider is None or 5062 // when it returns null response) 5063 // 2. standard autofill provider doesn't support inline suggestion 5064 // 3. we re-entered the autofill session and standard autofill was not re-triggered, this is 5065 // recognized by seeing mExpiredResponse == true 5066 final RemoteInlineSuggestionRenderService remoteRenderService = 5067 mService.getRemoteInlineSuggestionRenderServiceLocked(); 5068 if (remoteRenderService != null 5069 && (mSessionFlags.mAugmentedAutofillOnly 5070 || !mSessionFlags.mInlineSupportedByService 5071 || mSessionFlags.mExpiredResponse) 5072 && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { 5073 if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); 5074 remoteRenderService.getInlineSuggestionsRendererInfo( 5075 new RemoteCallback( 5076 new AugmentedAutofillInlineSuggestionRendererOnResultListener( 5077 this, focusedId, requestAugmentedAutofill), 5078 mHandler)); 5079 } else { 5080 requestAugmentedAutofill.accept( 5081 mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null)); 5082 } 5083 if (mAugmentedAutofillDestroyer == null) { 5084 mAugmentedAutofillDestroyer = remoteService::onDestroyAutofillWindowsRequest; 5085 } 5086 return mAugmentedAutofillDestroyer; 5087 } 5088 5089 private static class AugmentedAutofillInlineSuggestionRendererOnResultListener 5090 implements RemoteCallback.OnResultListener { 5091 5092 WeakReference<Session> mSessionWeakRef; 5093 final AutofillId mFocusedId; 5094 Consumer<InlineSuggestionsRequest> mRequestAugmentedAutofill; 5095 AugmentedAutofillInlineSuggestionRendererOnResultListener( Session session, AutofillId focussedId, Consumer<InlineSuggestionsRequest> requestAugmentedAutofill)5096 AugmentedAutofillInlineSuggestionRendererOnResultListener( 5097 Session session, 5098 AutofillId focussedId, 5099 Consumer<InlineSuggestionsRequest> requestAugmentedAutofill) { 5100 mSessionWeakRef = new WeakReference<>(session); 5101 mFocusedId = focussedId; 5102 mRequestAugmentedAutofill = requestAugmentedAutofill; 5103 } 5104 5105 @Override onResult(@ullable Bundle result)5106 public void onResult(@Nullable Bundle result) { 5107 Session session = mSessionWeakRef.get(); 5108 5109 if (logIfSessionNull( 5110 session, "AugmentedAutofillInlineSuggestionRendererOnResultListener:")) { 5111 return; 5112 } 5113 synchronized (session.mLock) { 5114 session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked( 5115 mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill, 5116 result); 5117 } 5118 } 5119 } 5120 5121 private static class AugmentedAutofillInlineSuggestionRequestConsumer 5122 implements Consumer<InlineSuggestionsRequest> { 5123 5124 WeakReference<Session> mSessionWeakRef; 5125 final AutofillId mFocusedId; 5126 final boolean mIsAllowlisted; 5127 final int mMode; 5128 final AutofillValue mCurrentValue; 5129 AugmentedAutofillInlineSuggestionRequestConsumer( Session session, AutofillId focussedId, boolean isAllowlisted, int mode, AutofillValue currentValue)5130 AugmentedAutofillInlineSuggestionRequestConsumer( 5131 Session session, 5132 AutofillId focussedId, 5133 boolean isAllowlisted, 5134 int mode, 5135 AutofillValue currentValue) { 5136 mSessionWeakRef = new WeakReference<>(session); 5137 mFocusedId = focussedId; 5138 mIsAllowlisted = isAllowlisted; 5139 mMode = mode; 5140 mCurrentValue = currentValue; 5141 5142 } 5143 @Override accept(InlineSuggestionsRequest inlineSuggestionsRequest)5144 public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) { 5145 Session session = mSessionWeakRef.get(); 5146 5147 if (logIfSessionNull( 5148 session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) { 5149 return; 5150 } 5151 session.onAugmentedAutofillInlineSuggestionAccept( 5152 inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue); 5153 5154 } 5155 } 5156 5157 private static class AugmentedAutofillInlineSuggestionsResponseCallback 5158 implements Function<InlineFillUi, Boolean> { 5159 5160 WeakReference<Session> mSessionWeakRef; 5161 AugmentedAutofillInlineSuggestionsResponseCallback(Session session)5162 AugmentedAutofillInlineSuggestionsResponseCallback(Session session) { 5163 this.mSessionWeakRef = new WeakReference<>(session); 5164 } 5165 5166 @Override apply(InlineFillUi inlineFillUi)5167 public Boolean apply(InlineFillUi inlineFillUi) { 5168 Session session = mSessionWeakRef.get(); 5169 5170 if (logIfSessionNull( 5171 session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) { 5172 return false; 5173 } 5174 5175 synchronized (session.mLock) { 5176 return session.mInlineSessionController.setInlineFillUiLocked(inlineFillUi); 5177 } 5178 } 5179 } 5180 5181 private static class AugmentedAutofillErrorCallback implements Runnable { 5182 5183 WeakReference<Session> mSessionWeakRef; 5184 AugmentedAutofillErrorCallback(Session session)5185 AugmentedAutofillErrorCallback(Session session) { 5186 this.mSessionWeakRef = new WeakReference<>(session); 5187 } 5188 5189 @Override run()5190 public void run() { 5191 Session session = mSessionWeakRef.get(); 5192 5193 if (logIfSessionNull(session, "AugmentedAutofillErrorCallback:")) { 5194 return; 5195 } 5196 session.onAugmentedAutofillErrorCallback(); 5197 } 5198 } 5199 5200 /** 5201 * If the session is null or has been destroyed, log the error msg, and return true. 5202 * This is a helper function intended to be called when de-referencing from a weak reference. 5203 * @param session 5204 * @param logPrefix 5205 * @return true if the session is null, false otherwise. 5206 */ logIfSessionNull(Session session, String logPrefix)5207 private static boolean logIfSessionNull(Session session, String logPrefix) { 5208 if (session == null) { 5209 Slog.wtf(TAG, logPrefix + " Session null"); 5210 return true; 5211 } 5212 if (session.mDestroyed) { 5213 // TODO: Update this to return in this block. We aren't doing this to preserve the 5214 // behavior, but can be modified once we have more time to soak the changes. 5215 Slog.w(TAG, logPrefix + " Session destroyed, but following through"); 5216 // Follow-through 5217 } 5218 return false; 5219 } 5220 onAugmentedAutofillInlineSuggestionAccept( InlineSuggestionsRequest inlineSuggestionsRequest, AutofillId focussedId, boolean isAllowlisted, int mode, AutofillValue currentValue)5221 private void onAugmentedAutofillInlineSuggestionAccept( 5222 InlineSuggestionsRequest inlineSuggestionsRequest, 5223 AutofillId focussedId, 5224 boolean isAllowlisted, 5225 int mode, 5226 AutofillValue currentValue) { 5227 synchronized (mLock) { 5228 final RemoteAugmentedAutofillService remoteService = 5229 mService.getRemoteAugmentedAutofillServiceLocked(); 5230 logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), 5231 focussedId, isAllowlisted, inlineSuggestionsRequest != null); 5232 remoteService.onRequestAutofillLocked(id, mClient, 5233 taskId, mComponentName, mActivityToken, 5234 AutofillId.withoutSession(focussedId), currentValue, 5235 inlineSuggestionsRequest, 5236 new AugmentedAutofillInlineSuggestionsResponseCallback(this), 5237 new AugmentedAutofillErrorCallback(this), 5238 mService.getRemoteInlineSuggestionRenderServiceLocked(), userId); 5239 } 5240 } 5241 onAugmentedAutofillErrorCallback()5242 private void onAugmentedAutofillErrorCallback() { 5243 synchronized (mLock) { 5244 cancelAugmentedAutofillLocked(); 5245 5246 // Also cancel augmented in IME 5247 mInlineSessionController.setInlineFillUiLocked( 5248 InlineFillUi.emptyUi(mCurrentViewId)); 5249 } 5250 } 5251 5252 @GuardedBy("mLock") cancelAugmentedAutofillLocked()5253 private void cancelAugmentedAutofillLocked() { 5254 final RemoteAugmentedAutofillService remoteService = mService 5255 .getRemoteAugmentedAutofillServiceLocked(); 5256 if (remoteService == null) { 5257 Slog.w(TAG, "cancelAugmentedAutofillLocked(): no service for user"); 5258 return; 5259 } 5260 if (sVerbose) Slog.v(TAG, "cancelAugmentedAutofillLocked() on " + mCurrentViewId); 5261 remoteService.onDestroyAutofillWindowsRequest(); 5262 } 5263 5264 @GuardedBy("mLock") processResponseLocked(@onNull FillResponse newResponse, @Nullable Bundle newClientState, int flags)5265 private void processResponseLocked(@NonNull FillResponse newResponse, 5266 @Nullable Bundle newClientState, int flags) { 5267 // Make sure we are hiding the UI which will be shown 5268 // only if handling the current response requires it. 5269 mUi.hideAll(this); 5270 5271 if ((newResponse.getFlags() & FillResponse.FLAG_DELAY_FILL) == 0) { 5272 Slog.d(TAG, "Service did not request to wait for delayed fill response."); 5273 unregisterDelayedFillBroadcastLocked(); 5274 } 5275 5276 final int requestId = newResponse.getRequestId(); 5277 if (sVerbose) { 5278 Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId 5279 + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse 5280 + ",newClientState=" + newClientState); 5281 } 5282 5283 if (mResponses == null) { 5284 // Set initial capacity as 2 to handle cases where service always requires auth. 5285 // TODO: add a metric for number of responses set by server, so we can use its average 5286 // as the initial array capacity. 5287 mResponses = new SparseArray<>(2); 5288 } 5289 mResponses.put(requestId, newResponse); 5290 mClientState = newClientState != null ? newClientState : newResponse.getClientState(); 5291 5292 List<Dataset> datasetList = newResponse.getDatasets(); 5293 5294 mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(sIdCounterForPcc.get()); 5295 mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId); 5296 mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList); 5297 5298 setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false); 5299 updateFillDialogTriggerIdsLocked(); 5300 updateTrackedIdsLocked(); 5301 5302 if (mCurrentViewId == null) { 5303 return; 5304 } 5305 5306 // Updates the UI, if necessary. 5307 final ViewState currentView = mViewStates.get(mCurrentViewId); 5308 currentView.maybeCallOnFillReady(flags); 5309 } 5310 5311 /** 5312 * Sets the state of all views in the given response. 5313 */ 5314 @GuardedBy("mLock") setViewStatesLocked(FillResponse response, int state, boolean clearResponse)5315 private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) { 5316 final List<Dataset> datasets = response.getDatasets(); 5317 if (datasets != null && !datasets.isEmpty()) { 5318 for (int i = 0; i < datasets.size(); i++) { 5319 final Dataset dataset = datasets.get(i); 5320 if (dataset == null) { 5321 Slog.w(TAG, "Ignoring null dataset on " + datasets); 5322 continue; 5323 } 5324 setViewStatesLocked(response, dataset, state, clearResponse); 5325 } 5326 } else if (response.getAuthentication() != null) { 5327 for (AutofillId autofillId : response.getAuthenticationIds()) { 5328 final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null); 5329 if (!clearResponse) { 5330 viewState.setResponse(response); 5331 } else { 5332 viewState.setResponse(null); 5333 } 5334 } 5335 } 5336 final SaveInfo saveInfo = response.getSaveInfo(); 5337 if (saveInfo != null) { 5338 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 5339 if (requiredIds != null) { 5340 for (AutofillId id : requiredIds) { 5341 createOrUpdateViewStateLocked(id, state, null); 5342 } 5343 } 5344 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 5345 if (optionalIds != null) { 5346 for (AutofillId id : optionalIds) { 5347 createOrUpdateViewStateLocked(id, state, null); 5348 } 5349 } 5350 } 5351 5352 final AutofillId[] authIds = response.getAuthenticationIds(); 5353 if (authIds != null) { 5354 for (AutofillId id : authIds) { 5355 createOrUpdateViewStateLocked(id, state, null); 5356 } 5357 } 5358 } 5359 5360 /** 5361 * Sets the state and response of all views in the given dataset. 5362 */ 5363 @GuardedBy("mLock") setViewStatesLocked(@ullable FillResponse response, @NonNull Dataset dataset, int state, boolean clearResponse)5364 private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset, 5365 int state, boolean clearResponse) { 5366 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 5367 final ArrayList<AutofillValue> values = dataset.getFieldValues(); 5368 for (int j = 0; j < ids.size(); j++) { 5369 final AutofillId id = ids.get(j); 5370 final AutofillValue value = values.get(j); 5371 final ViewState viewState = createOrUpdateViewStateLocked(id, state, value); 5372 final String datasetId = dataset.getId(); 5373 if (datasetId != null) { 5374 viewState.setDatasetId(datasetId); 5375 } 5376 if (clearResponse) { 5377 viewState.setResponse(null); 5378 } else if (response != null) { 5379 viewState.setResponse(response); 5380 } 5381 } 5382 } 5383 5384 @GuardedBy("mLock") createOrUpdateViewStateLocked(@onNull AutofillId id, int state, @Nullable AutofillValue value)5385 private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state, 5386 @Nullable AutofillValue value) { 5387 ViewState viewState = mViewStates.get(id); 5388 if (viewState != null) { 5389 viewState.setState(state); 5390 } else { 5391 viewState = new ViewState(id, this, state); 5392 if (sVerbose) { 5393 Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state); 5394 } 5395 viewState.setCurrentValue(findValueLocked(id)); 5396 mViewStates.put(id, viewState); 5397 } 5398 if ((state & ViewState.STATE_AUTOFILLED) != 0) { 5399 viewState.setAutofilledValue(value); 5400 } 5401 return viewState; 5402 } 5403 autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, int uiType)5404 void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, 5405 int uiType) { 5406 if (sDebug) { 5407 Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex 5408 + "; dataset=" + dataset); 5409 } 5410 synchronized (mLock) { 5411 if (mDestroyed) { 5412 Slog.w(TAG, "Call to Session#autoFill() rejected - session: " 5413 + id + " destroyed"); 5414 return; 5415 } 5416 // Selected dataset id is logged regardless of authentication result. 5417 mPresentationStatsEventLogger.maybeSetSelectedDatasetId(datasetIndex); 5418 mPresentationStatsEventLogger.maybeSetSelectedDatasetPickReason( 5419 dataset.getEligibleReason()); 5420 // Autofill it directly... 5421 if (dataset.getAuthentication() == null) { 5422 if (generateEvent) { 5423 mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType); 5424 } 5425 if (mCurrentViewId != null) { 5426 mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); 5427 } 5428 autoFillApp(dataset); 5429 return; 5430 } 5431 5432 // ...or handle authentication. 5433 mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType); 5434 mPresentationStatsEventLogger.maybeSetAuthenticationType( 5435 AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); 5436 setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); 5437 final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState); 5438 if (fillInIntent == null) { 5439 forceRemoveFromServiceLocked(); 5440 return; 5441 } 5442 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, 5443 datasetIndex); 5444 startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent, 5445 /* authenticateInline= */false); 5446 5447 } 5448 } 5449 5450 // TODO: this should never be null, but we got at least one occurrence, probably due to a race. 5451 @GuardedBy("mLock") 5452 @Nullable createAuthFillInIntentLocked(int requestId, Bundle extras)5453 private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) { 5454 final Intent fillInIntent = new Intent(); 5455 5456 final FillContext context = getFillContextByRequestIdLocked(requestId); 5457 5458 if (context == null) { 5459 wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s", 5460 requestId, mContexts); 5461 return null; 5462 } 5463 if (mLastInlineSuggestionsRequest != null 5464 && mLastInlineSuggestionsRequest.first == requestId) { 5465 fillInIntent.putExtra(AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST, 5466 mLastInlineSuggestionsRequest.second); 5467 } 5468 fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure()); 5469 fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras); 5470 return fillInIntent; 5471 } 5472 5473 @NonNull inlineSuggestionsRequestCacheDecorator( @onNull Consumer<InlineSuggestionsRequest> consumer, int requestId)5474 Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestCacheDecorator( 5475 @NonNull Consumer<InlineSuggestionsRequest> consumer, int requestId) { 5476 return inlineSuggestionsRequest -> { 5477 consumer.accept(inlineSuggestionsRequest); 5478 synchronized (mLock) { 5479 mLastInlineSuggestionsRequest = Pair.create(requestId, inlineSuggestionsRequest); 5480 } 5481 }; 5482 } 5483 getDetectionPreferenceForLogging()5484 private int getDetectionPreferenceForLogging() { 5485 if (mService.isPccClassificationEnabled()) { 5486 if (mService.getMaster().preferProviderOverPcc()) { 5487 return DETECTION_PREFER_AUTOFILL_PROVIDER; 5488 } 5489 return DETECTION_PREFER_PCC; 5490 } 5491 return DETECTION_PREFER_UNKNOWN; 5492 } 5493 startNewEventForPresentationStatsEventLogger()5494 private void startNewEventForPresentationStatsEventLogger() { 5495 synchronized (mLock) { 5496 mPresentationStatsEventLogger.startNewEvent(); 5497 mPresentationStatsEventLogger.maybeSetDetectionPreference( 5498 getDetectionPreferenceForLogging()); 5499 mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); 5500 } 5501 } 5502 startAuthentication(int authenticationId, IntentSender intent, Intent fillInIntent, boolean authenticateInline)5503 private void startAuthentication(int authenticationId, IntentSender intent, 5504 Intent fillInIntent, boolean authenticateInline) { 5505 try { 5506 synchronized (mLock) { 5507 mClient.authenticate(id, authenticationId, intent, fillInIntent, 5508 authenticateInline); 5509 } 5510 } catch (RemoteException e) { 5511 Slog.e(TAG, "Error launching auth intent", e); 5512 } 5513 } 5514 5515 /** 5516 * The result of checking whether to show the save dialog, when session can be saved. 5517 * 5518 * @hide 5519 */ 5520 static final class SaveResult { 5521 /** 5522 * Whether to record the save dialog has been shown. 5523 */ 5524 private boolean mLogSaveShown; 5525 5526 /** 5527 * Whether to remove the session. 5528 */ 5529 private boolean mRemoveSession; 5530 5531 /** 5532 * The reason why a save dialog was not shown. 5533 */ 5534 @NoSaveReason private int mSaveDialogNotShowReason; 5535 SaveResult(boolean logSaveShown, boolean removeSession, @NoSaveReason int saveDialogNotShowReason)5536 SaveResult(boolean logSaveShown, boolean removeSession, 5537 @NoSaveReason int saveDialogNotShowReason) { 5538 mLogSaveShown = logSaveShown; 5539 mRemoveSession = removeSession; 5540 mSaveDialogNotShowReason = saveDialogNotShowReason; 5541 } 5542 5543 /** 5544 * Returns whether to record the save dialog has been shown. 5545 * 5546 * @return Whether to record the save dialog has been shown. 5547 */ isLogSaveShown()5548 public boolean isLogSaveShown() { 5549 return mLogSaveShown; 5550 } 5551 5552 /** 5553 * Sets whether to record the save dialog has been shown. 5554 * 5555 * @param logSaveShown Whether to record the save dialog has been shown. 5556 */ setLogSaveShown(boolean logSaveShown)5557 public void setLogSaveShown(boolean logSaveShown) { 5558 mLogSaveShown = logSaveShown; 5559 } 5560 5561 /** 5562 * Returns whether to remove the session. 5563 * 5564 * @return Whether to remove the session. 5565 */ isRemoveSession()5566 public boolean isRemoveSession() { 5567 return mRemoveSession; 5568 } 5569 5570 /** 5571 * Sets whether to remove the session. 5572 * 5573 * @param removeSession Whether to remove the session. 5574 */ setRemoveSession(boolean removeSession)5575 public void setRemoveSession(boolean removeSession) { 5576 mRemoveSession = removeSession; 5577 } 5578 5579 /** 5580 * Returns the reason why a save dialog was not shown. 5581 * 5582 * @return The reason why a save dialog was not shown. 5583 */ 5584 @NoSaveReason getNoSaveUiReason()5585 public int getNoSaveUiReason() { 5586 return mSaveDialogNotShowReason; 5587 } 5588 5589 /** 5590 * Sets the reason why a save dialog was not shown. 5591 * 5592 * @param saveDialogNotShowReason The reason why a save dialog was not shown. 5593 */ setSaveDialogNotShowReason(@oSaveReason int saveDialogNotShowReason)5594 public void setSaveDialogNotShowReason(@NoSaveReason int saveDialogNotShowReason) { 5595 mSaveDialogNotShowReason = saveDialogNotShowReason; 5596 } 5597 5598 @Override toString()5599 public String toString() { 5600 return "SaveResult: [logSaveShown=" + mLogSaveShown 5601 + ", removeSession=" + mRemoveSession 5602 + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason + "]"; 5603 } 5604 } 5605 5606 /** 5607 * Class maintaining the state of the requests to 5608 * {@link android.service.assist.classification.FieldClassificationService}. 5609 */ 5610 private static final class ClassificationState { 5611 5612 /** 5613 * Initial state indicating that the request for classification hasn't been triggered yet. 5614 */ 5615 private static final int STATE_INITIAL = 1; 5616 /** 5617 * Assist request has been triggered, but awaiting response. 5618 */ 5619 private static final int STATE_PENDING_ASSIST_REQUEST = 2; 5620 /** 5621 * Classification request has been triggered, but awaiting response. 5622 */ 5623 private static final int STATE_PENDING_REQUEST = 3; 5624 /** 5625 * Classification response has been received. 5626 */ 5627 private static final int STATE_RESPONSE = 4; 5628 /** 5629 * Classification state has been invalidated, and the last response may no longer be valid. 5630 * This could occur due to various reasons like views changing their layouts, becoming 5631 * visible or invisible, thereby rendering previous response potentially inaccurate or 5632 * incomplete. 5633 */ 5634 private static final int STATE_INVALIDATED = 5; 5635 5636 @IntDef(prefix = { "STATE_" }, value = { 5637 STATE_INITIAL, 5638 STATE_PENDING_ASSIST_REQUEST, 5639 STATE_PENDING_REQUEST, 5640 STATE_RESPONSE, 5641 STATE_INVALIDATED 5642 }) 5643 @Retention(RetentionPolicy.SOURCE) 5644 @interface ClassificationRequestState{} 5645 5646 @GuardedBy("mLock") 5647 private @ClassificationRequestState int mState = STATE_INITIAL; 5648 5649 @GuardedBy("mLock") 5650 private FieldClassificationRequest mPendingFieldClassificationRequest; 5651 5652 @GuardedBy("mLock") 5653 private FieldClassificationResponse mLastFieldClassificationResponse; 5654 5655 @GuardedBy("mLock") 5656 private ArrayMap<AutofillId, Set<String>> mClassificationHintsMap; 5657 5658 @GuardedBy("mLock") 5659 private ArrayMap<AutofillId, Set<String>> mClassificationGroupHintsMap; 5660 5661 @GuardedBy("mLock") 5662 private ArrayMap<AutofillId, Set<String>> mClassificationCombinedHintsMap; 5663 5664 /** 5665 * Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint 5666 * being applicable to many types. An example of this being new/change password forms, 5667 * where you need to confirm the passward twice. 5668 */ 5669 @GuardedBy("mLock") 5670 private ArrayMap<String, Set<AutofillId>> mHintsToAutofillIdMap; 5671 5672 /** 5673 * Group hints are expected to have a 1:many mapping. For example, different credit card 5674 * fields (creditCardNumber, expiry, cvv) will all map to the same group hints. 5675 */ 5676 @GuardedBy("mLock") 5677 private ArrayMap<String, Set<AutofillId>> mGroupHintsToAutofillIdMap; 5678 5679 @GuardedBy("mLock") stateToString()5680 private String stateToString() { 5681 switch (mState) { 5682 case STATE_INITIAL: 5683 return "STATE_INITIAL"; 5684 case STATE_PENDING_ASSIST_REQUEST: 5685 return "STATE_PENDING_ASSIST_REQUEST"; 5686 case STATE_PENDING_REQUEST: 5687 return "STATE_PENDING_REQUEST"; 5688 case STATE_RESPONSE: 5689 return "STATE_RESPONSE"; 5690 case STATE_INVALIDATED: 5691 return "STATE_INVALIDATED"; 5692 default: 5693 return "UNKNOWN_CLASSIFICATION_STATE_" + mState; 5694 } 5695 } 5696 5697 /** 5698 * Process the response received. 5699 * @return true if the response was processed, false otherwise. If there wasn't any 5700 * response, yet this function was called, it would return false. 5701 */ 5702 @GuardedBy("mLock") processResponse()5703 private boolean processResponse() { 5704 if (mClassificationHintsMap != null && !mClassificationHintsMap.isEmpty()) { 5705 // Already processed, so return 5706 return true; 5707 } 5708 5709 FieldClassificationResponse response = mLastFieldClassificationResponse; 5710 if (response == null) return false; 5711 5712 mClassificationHintsMap = new ArrayMap<>(); 5713 mClassificationGroupHintsMap = new ArrayMap<>(); 5714 mHintsToAutofillIdMap = new ArrayMap<>(); 5715 mGroupHintsToAutofillIdMap = new ArrayMap<>(); 5716 mClassificationCombinedHintsMap = new ArrayMap<>(); 5717 Set<android.service.assist.classification.FieldClassification> classifications = 5718 response.getClassifications(); 5719 5720 for (android.service.assist.classification.FieldClassification classification : 5721 classifications) { 5722 AutofillId id = classification.getAutofillId(); 5723 Set<String> hintDetections = classification.getHints(); 5724 Set<String> groupHintsDetections = classification.getGroupHints(); 5725 ArraySet<String> combinedHints = new ArraySet<>(hintDetections); 5726 mClassificationHintsMap.put(id, hintDetections); 5727 if (groupHintsDetections != null) { 5728 mClassificationGroupHintsMap.put(id, groupHintsDetections); 5729 combinedHints.addAll(groupHintsDetections); 5730 } 5731 mClassificationCombinedHintsMap.put(id, combinedHints); 5732 5733 processDetections(hintDetections, id, mHintsToAutofillIdMap); 5734 processDetections(groupHintsDetections, id, mGroupHintsToAutofillIdMap); 5735 } 5736 return true; 5737 } 5738 5739 @GuardedBy("mLock") processDetections(Set<String> detections, AutofillId id, ArrayMap<String, Set<AutofillId>> currentMap)5740 private static void processDetections(Set<String> detections, AutofillId id, 5741 ArrayMap<String, Set<AutofillId>> currentMap) { 5742 for (String detection : detections) { 5743 Set<AutofillId> autofillIds; 5744 if (currentMap.containsKey(detection)) { 5745 autofillIds = currentMap.get(detection); 5746 } else { 5747 autofillIds = new ArraySet<>(); 5748 } 5749 autofillIds.add(id); 5750 currentMap.put(detection, autofillIds); 5751 } 5752 } 5753 5754 @GuardedBy("mLock") invalidateState()5755 private void invalidateState() { 5756 mState = STATE_INVALIDATED; 5757 } 5758 5759 @GuardedBy("mLock") updatePendingAssistData()5760 private void updatePendingAssistData() { 5761 mState = STATE_PENDING_ASSIST_REQUEST; 5762 } 5763 5764 @GuardedBy("mLock") updatePendingRequest()5765 private void updatePendingRequest() { 5766 mState = STATE_PENDING_REQUEST; 5767 } 5768 5769 @GuardedBy("mLock") updateResponseReceived(FieldClassificationResponse response)5770 private void updateResponseReceived(FieldClassificationResponse response) { 5771 mState = STATE_RESPONSE; 5772 mLastFieldClassificationResponse = response; 5773 mPendingFieldClassificationRequest = null; 5774 processResponse(); 5775 } 5776 5777 @GuardedBy("mLock") onAssistStructureReceived(AssistStructure structure)5778 private void onAssistStructureReceived(AssistStructure structure) { 5779 mState = STATE_PENDING_REQUEST; 5780 mPendingFieldClassificationRequest = new FieldClassificationRequest(structure); 5781 } 5782 5783 @GuardedBy("mLock") onFieldClassificationRequestSent()5784 private void onFieldClassificationRequestSent() { 5785 mState = STATE_PENDING_REQUEST; 5786 mPendingFieldClassificationRequest = null; 5787 } 5788 5789 @GuardedBy("mLock") shouldTriggerRequest()5790 private boolean shouldTriggerRequest() { 5791 return mState == STATE_INITIAL || mState == STATE_INVALIDATED; 5792 } 5793 5794 @GuardedBy("mLock") 5795 @Override toString()5796 public String toString() { 5797 return "ClassificationState: [" 5798 + "state=" + stateToString() 5799 + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest 5800 + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse 5801 + ", mClassificationHintsMap=" + mClassificationHintsMap 5802 + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap 5803 + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap 5804 + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap 5805 + "]"; 5806 } 5807 5808 } 5809 5810 @Override toString()5811 public String toString() { 5812 return "Session: [id=" + id + ", component=" + mComponentName 5813 + ", state=" + sessionStateAsString(mSessionState) + "]"; 5814 } 5815 5816 @GuardedBy("mLock") dumpLocked(String prefix, PrintWriter pw)5817 void dumpLocked(String prefix, PrintWriter pw) { 5818 final String prefix2 = prefix + " "; 5819 pw.print(prefix); pw.print("id: "); pw.println(id); 5820 pw.print(prefix); pw.print("uid: "); pw.println(uid); 5821 pw.print(prefix); pw.print("taskId: "); pw.println(taskId); 5822 pw.print(prefix); pw.print("flags: "); pw.println(mFlags); 5823 pw.print(prefix); pw.print("displayId: "); pw.println(mContext.getDisplayId()); 5824 pw.print(prefix); pw.print("state: "); pw.println(sessionStateAsString(mSessionState)); 5825 pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); 5826 pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); 5827 pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); 5828 pw.print(prefix); pw.print("Time to show UI: "); 5829 if (mUiShownTime == 0) { 5830 pw.println("N/A"); 5831 } else { 5832 TimeUtils.formatDuration(mUiShownTime - mStartTime, pw); 5833 pw.println(); 5834 } 5835 final int requestLogsSizes = mRequestLogs.size(); 5836 pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes); 5837 for (int i = 0; i < requestLogsSizes; i++) { 5838 final int requestId = mRequestLogs.keyAt(i); 5839 final LogMaker log = mRequestLogs.valueAt(i); 5840 pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req="); 5841 pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println(); 5842 } 5843 pw.print(prefix); pw.print("mResponses: "); 5844 if (mResponses == null) { 5845 pw.println("null"); 5846 } else { 5847 pw.println(mResponses.size()); 5848 for (int i = 0; i < mResponses.size(); i++) { 5849 pw.print(prefix2); pw.print('#'); pw.print(i); 5850 pw.print(' '); pw.println(mResponses.valueAt(i)); 5851 } 5852 } 5853 pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); 5854 pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); 5855 pw.print(prefix); pw.print("mShowingSaveUi: "); pw.println(mSessionFlags.mShowingSaveUi); 5856 pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi); 5857 final int numberViews = mViewStates.size(); 5858 pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); 5859 for (int i = 0; i < numberViews; i++) { 5860 pw.print(prefix); pw.print("ViewState at #"); pw.println(i); 5861 mViewStates.valueAt(i).dump(prefix2, pw); 5862 } 5863 5864 pw.print(prefix); pw.print("mContexts: " ); 5865 if (mContexts != null) { 5866 int numContexts = mContexts.size(); 5867 for (int i = 0; i < numContexts; i++) { 5868 FillContext context = mContexts.get(i); 5869 5870 pw.print(prefix2); pw.print(context); 5871 if (sVerbose) { 5872 pw.println("AssistStructure dumped at logcat)"); 5873 5874 // TODO: add method on AssistStructure to dump on pw 5875 context.getStructure().dump(false); 5876 } 5877 } 5878 } else { 5879 pw.println("null"); 5880 } 5881 5882 pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); 5883 if (mClientState != null) { 5884 pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw 5885 .println(" bytes"); 5886 } 5887 pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode); 5888 pw.print(prefix); pw.print("mUrlBar: "); 5889 if (mUrlBar == null) { 5890 pw.println("N/A"); 5891 } else { 5892 pw.print("id="); pw.print(mUrlBar.getAutofillId()); 5893 pw.print(" domain="); pw.print(mUrlBar.getWebDomain()); 5894 pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText()); 5895 } 5896 pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( 5897 mSaveOnAllViewsInvisible); 5898 pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); 5899 if (mSessionFlags.mAugmentedAutofillOnly) { 5900 pw.print(prefix); pw.println("For Augmented Autofill Only"); 5901 } 5902 if (mSessionFlags.mFillDialogDisabled) { 5903 pw.print(prefix); pw.println("Fill Dialog disabled"); 5904 } 5905 if (mLastFillDialogTriggerIds != null) { 5906 pw.print(prefix); pw.println("Last Fill Dialog trigger ids: "); 5907 pw.println(mSelectedDatasetIds); 5908 } 5909 if (mAugmentedAutofillDestroyer != null) { 5910 pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); 5911 } 5912 if (mAugmentedRequestsLogs != null) { 5913 pw.print(prefix); pw.print("number augmented requests: "); 5914 pw.println(mAugmentedRequestsLogs.size()); 5915 } 5916 5917 if (mAugmentedAutofillableIds != null) { 5918 pw.print(prefix); pw.print("mAugmentedAutofillableIds: "); 5919 pw.println(mAugmentedAutofillableIds); 5920 } 5921 if (mRemoteFillService != null) { 5922 mRemoteFillService.dump(prefix, pw); 5923 } 5924 } 5925 dumpRequestLog(@onNull PrintWriter pw, @NonNull LogMaker log)5926 private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) { 5927 pw.print("CAT="); pw.print(log.getCategory()); 5928 pw.print(", TYPE="); 5929 final int type = log.getType(); 5930 switch (type) { 5931 case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break; 5932 case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break; 5933 case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break; 5934 default: pw.print("UNSUPPORTED"); 5935 } 5936 pw.print('('); pw.print(type); pw.print(')'); 5937 pw.print(", PKG="); pw.print(log.getPackageName()); 5938 pw.print(", SERVICE="); pw.print(log 5939 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE)); 5940 pw.print(", ORDINAL="); pw.print(log 5941 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL)); 5942 dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS); 5943 dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS); 5944 dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION); 5945 final int authStatus = 5946 getNumericValue(log, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS); 5947 if (authStatus != 0) { 5948 pw.print(", AUTH_STATUS="); 5949 switch (authStatus) { 5950 case MetricsEvent.AUTOFILL_AUTHENTICATED: 5951 pw.print("AUTHENTICATED"); break; 5952 case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED: 5953 pw.print("DATASET_AUTHENTICATED"); break; 5954 case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION: 5955 pw.print("INVALID_AUTHENTICATION"); break; 5956 case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION: 5957 pw.print("INVALID_DATASET_AUTHENTICATION"); break; 5958 default: pw.print("UNSUPPORTED"); 5959 } 5960 pw.print('('); pw.print(authStatus); pw.print(')'); 5961 } 5962 dumpNumericValue(pw, log, "FC_IDS", 5963 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS); 5964 dumpNumericValue(pw, log, "COMPAT_MODE", 5965 MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE); 5966 } 5967 dumpNumericValue(@onNull PrintWriter pw, @NonNull LogMaker log, @NonNull String field, int tag)5968 private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log, 5969 @NonNull String field, int tag) { 5970 final int value = getNumericValue(log, tag); 5971 if (value != 0) { 5972 pw.print(", "); pw.print(field); pw.print('='); pw.print(value); 5973 } 5974 } 5975 autoFillApp(Dataset dataset)5976 void autoFillApp(Dataset dataset) { 5977 synchronized (mLock) { 5978 if (mDestroyed) { 5979 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: " 5980 + id + " destroyed"); 5981 return; 5982 } 5983 try { 5984 // Skip null values as a null values means no change 5985 final int entryCount = dataset.getFieldIds().size(); 5986 final List<AutofillId> ids = new ArrayList<>(entryCount); 5987 final List<AutofillValue> values = new ArrayList<>(entryCount); 5988 boolean waitingDatasetAuth = false; 5989 boolean hideHighlight = (entryCount == 1 5990 && dataset.getFieldIds().get(0).equals(mCurrentViewId)); 5991 for (int i = 0; i < entryCount; i++) { 5992 if (dataset.getFieldValues().get(i) == null) { 5993 continue; 5994 } 5995 final AutofillId viewId = dataset.getFieldIds().get(i); 5996 ids.add(viewId); 5997 values.add(dataset.getFieldValues().get(i)); 5998 final ViewState viewState = mViewStates.get(viewId); 5999 if (viewState != null 6000 && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) { 6001 if (sVerbose) { 6002 Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth"); 6003 } 6004 waitingDatasetAuth = true; 6005 viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH); 6006 } 6007 } 6008 if (!ids.isEmpty()) { 6009 if (waitingDatasetAuth) { 6010 mUi.hideFillUi(this); 6011 } 6012 if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); 6013 6014 mClient.autofill(id, ids, values, hideHighlight); 6015 if (dataset.getId() != null) { 6016 if (mSelectedDatasetIds == null) { 6017 mSelectedDatasetIds = new ArrayList<>(); 6018 } 6019 mSelectedDatasetIds.add(dataset.getId()); 6020 } 6021 setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false); 6022 } 6023 } catch (RemoteException e) { 6024 Slog.w(TAG, "Error autofilling activity: " + e); 6025 } 6026 } 6027 } 6028 getUiForShowing()6029 private AutoFillUI getUiForShowing() { 6030 synchronized (mLock) { 6031 mUi.setCallback(this); 6032 return mUi; 6033 } 6034 } 6035 6036 @GuardedBy("mLock") logAllEvents(@utofillCommitReason int val)6037 private void logAllEvents(@AutofillCommitReason int val) { 6038 mSessionCommittedEventLogger.maybeSetCommitReason(val); 6039 mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); 6040 mSessionCommittedEventLogger.maybeSetSessionDurationMillis( 6041 SystemClock.elapsedRealtime() - mStartTime); 6042 mFillRequestEventLogger.logAndEndEvent(); 6043 mFillResponseEventLogger.logAndEndEvent(); 6044 mPresentationStatsEventLogger.logAndEndEvent(); 6045 mSaveEventLogger.logAndEndEvent(); 6046 mSessionCommittedEventLogger.logAndEndEvent(); 6047 } 6048 6049 /** 6050 * Destroy this session and perform any clean up work. 6051 * 6052 * <p>Typically called in 2 scenarios: 6053 * 6054 * <ul> 6055 * <li>When the session naturally finishes (i.e., from {@link #removeFromServiceLocked()}. 6056 * <li>When the service hosting the session is finished (for example, because the user 6057 * disabled it). 6058 * </ul> 6059 */ 6060 @GuardedBy("mLock") destroyLocked()6061 RemoteFillService destroyLocked() { 6062 // Log unlogged events. 6063 logAllEvents(COMMIT_REASON_SESSION_DESTROYED); 6064 6065 if (mDestroyed) { 6066 return null; 6067 } 6068 6069 clearPendingIntentLocked(); 6070 unregisterDelayedFillBroadcastLocked(); 6071 6072 unlinkClientVultureLocked(); 6073 mUi.destroyAll(mPendingSaveUi, this, true); 6074 mUi.clearCallback(this); 6075 if (mCurrentViewId != null) { 6076 mInlineSessionController.destroyLocked(mCurrentViewId); 6077 } 6078 final RemoteInlineSuggestionRenderService remoteRenderService = 6079 mService.getRemoteInlineSuggestionRenderServiceLocked(); 6080 if (remoteRenderService != null) { 6081 remoteRenderService.destroySuggestionViews(userId, id); 6082 } 6083 6084 mDestroyed = true; 6085 6086 // Log metrics 6087 final int totalRequests = mRequestLogs.size(); 6088 if (totalRequests > 0) { 6089 if (sVerbose) Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " requests"); 6090 for (int i = 0; i < totalRequests; i++) { 6091 final LogMaker log = mRequestLogs.valueAt(i); 6092 mMetricsLogger.write(log); 6093 } 6094 } 6095 6096 final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0 6097 : mAugmentedRequestsLogs.size(); 6098 if (totalAugmentedRequests > 0) { 6099 if (sVerbose) { 6100 Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests"); 6101 } 6102 for (int i = 0; i < totalAugmentedRequests; i++) { 6103 final LogMaker log = mAugmentedRequestsLogs.get(i); 6104 mMetricsLogger.write(log); 6105 } 6106 } 6107 6108 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) 6109 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests); 6110 if (totalAugmentedRequests > 0) { 6111 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, 6112 totalAugmentedRequests); 6113 } 6114 if (mSessionFlags.mAugmentedAutofillOnly) { 6115 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_AUGMENTED_ONLY, 1); 6116 } 6117 mMetricsLogger.write(log); 6118 6119 return mRemoteFillService; 6120 } 6121 6122 /** 6123 * Destroy this session and remove it from the service always, even if it does have a pending 6124 * Save UI. 6125 */ 6126 @GuardedBy("mLock") forceRemoveFromServiceLocked()6127 void forceRemoveFromServiceLocked() { 6128 forceRemoveFromServiceLocked(AutofillManager.STATE_UNKNOWN); 6129 } 6130 6131 @GuardedBy("mLock") forceRemoveFromServiceIfForAugmentedOnlyLocked()6132 void forceRemoveFromServiceIfForAugmentedOnlyLocked() { 6133 if (sVerbose) { 6134 Slog.v(TAG, "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + this.id + "): " 6135 + mSessionFlags.mAugmentedAutofillOnly); 6136 } 6137 if (!mSessionFlags.mAugmentedAutofillOnly) return; 6138 6139 forceRemoveFromServiceLocked(); 6140 } 6141 6142 @GuardedBy("mLock") forceRemoveFromServiceLocked(int clientState)6143 void forceRemoveFromServiceLocked(int clientState) { 6144 if (sVerbose) Slog.v(TAG, "forceRemoveFromServiceLocked(): " + mPendingSaveUi); 6145 6146 final boolean isPendingSaveUi = isSaveUiPendingLocked(); 6147 mPendingSaveUi = null; 6148 removeFromServiceLocked(); 6149 mUi.destroyAll(mPendingSaveUi, this, false); 6150 if (!isPendingSaveUi) { 6151 try { 6152 mClient.setSessionFinished(clientState, /* autofillableIds= */ null); 6153 } catch (RemoteException e) { 6154 Slog.e(TAG, "Error notifying client to finish session", e); 6155 } 6156 } 6157 destroyAugmentedAutofillWindowsLocked(); 6158 } 6159 6160 @GuardedBy("mLock") destroyAugmentedAutofillWindowsLocked()6161 void destroyAugmentedAutofillWindowsLocked() { 6162 if (mAugmentedAutofillDestroyer != null) { 6163 mAugmentedAutofillDestroyer.run(); 6164 mAugmentedAutofillDestroyer = null; 6165 } 6166 } 6167 6168 /** 6169 * Thread-safe version of {@link #removeFromServiceLocked()}. 6170 */ removeFromService()6171 private void removeFromService() { 6172 synchronized (mLock) { 6173 removeFromServiceLocked(); 6174 } 6175 } 6176 6177 /** 6178 * Destroy this session and remove it from the service, but but only if it does not have a 6179 * pending Save UI. 6180 */ 6181 @GuardedBy("mLock") removeFromServiceLocked()6182 void removeFromServiceLocked() { 6183 if (sVerbose) Slog.v(TAG, "removeFromServiceLocked(" + this.id + "): " + mPendingSaveUi); 6184 if (mDestroyed) { 6185 Slog.w(TAG, "Call to Session#removeFromServiceLocked() rejected - session: " 6186 + id + " destroyed"); 6187 return; 6188 } 6189 if (isSaveUiPendingLocked()) { 6190 Slog.i(TAG, "removeFromServiceLocked() ignored, waiting for pending save ui"); 6191 return; 6192 } 6193 6194 final RemoteFillService remoteFillService = destroyLocked(); 6195 mService.removeSessionLocked(id); 6196 if (remoteFillService != null) { 6197 remoteFillService.destroy(); 6198 } 6199 mSessionState = STATE_REMOVED; 6200 } 6201 onPendingSaveUi(int operation, @NonNull IBinder token)6202 void onPendingSaveUi(int operation, @NonNull IBinder token) { 6203 getUiForShowing().onPendingSaveUi(operation, token); 6204 } 6205 6206 /** 6207 * Checks whether this session is hiding the Save UI to handle a custom description link for 6208 * a specific {@code token} created by 6209 * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}. 6210 */ 6211 @GuardedBy("mLock") isSaveUiPendingForTokenLocked(@onNull IBinder token)6212 boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) { 6213 return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken()); 6214 } 6215 6216 /** 6217 * Checks whether this session is hiding the Save UI to handle a custom description link. 6218 */ 6219 @GuardedBy("mLock") isSaveUiPendingLocked()6220 private boolean isSaveUiPendingLocked() { 6221 return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING; 6222 } 6223 6224 @GuardedBy("mLock") getLastResponseIndexLocked()6225 private int getLastResponseIndexLocked() { 6226 // The response ids are monotonically increasing so 6227 // we just find the largest id which is the last. We 6228 // do not rely on the internal ordering in sparse 6229 // array to avoid - wow this stopped working!? 6230 int lastResponseIdx = -1; 6231 int lastResponseId = -1; 6232 if (mResponses != null) { 6233 final int responseCount = mResponses.size(); 6234 for (int i = 0; i < responseCount; i++) { 6235 if (mResponses.keyAt(i) > lastResponseId) { 6236 lastResponseIdx = i; 6237 lastResponseId = mResponses.keyAt(i); 6238 } 6239 } 6240 } 6241 return lastResponseIdx; 6242 } 6243 newLogMaker(int category)6244 private LogMaker newLogMaker(int category) { 6245 return newLogMaker(category, mService.getServicePackageName()); 6246 } 6247 newLogMaker(int category, String servicePackageName)6248 private LogMaker newLogMaker(int category, String servicePackageName) { 6249 return Helper.newLogMaker(category, mComponentName, servicePackageName, id, mCompatMode); 6250 } 6251 writeLog(int category)6252 private void writeLog(int category) { 6253 mMetricsLogger.write(newLogMaker(category)); 6254 } 6255 6256 @GuardedBy("mLock") logAuthenticationStatusLocked(int requestId, int status)6257 private void logAuthenticationStatusLocked(int requestId, int status) { 6258 addTaggedDataToRequestLogLocked(requestId, 6259 MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status); 6260 } 6261 6262 @GuardedBy("mLock") addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value)6263 private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) { 6264 final LogMaker requestLog = mRequestLogs.get(requestId); 6265 if (requestLog == null) { 6266 Slog.w(TAG, 6267 "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId); 6268 return; 6269 } 6270 requestLog.addTaggedData(tag, value); 6271 } 6272 6273 @GuardedBy("mLock") logAugmentedAutofillRequestLocked(int mode, ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted, Boolean isInline)6274 private void logAugmentedAutofillRequestLocked(int mode, 6275 ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted, 6276 Boolean isInline) { 6277 final String historyItem = 6278 "aug:id=" + id + " u=" + uid + " m=" + mode 6279 + " a=" + ComponentName.flattenToShortString(mComponentName) 6280 + " f=" + focusedId 6281 + " s=" + augmentedRemoteServiceName 6282 + " w=" + isWhitelisted 6283 + " i=" + isInline; 6284 mService.getMaster().logRequestLocked(historyItem); 6285 } 6286 wtf(@ullable Exception e, String fmt, Object...args)6287 private void wtf(@Nullable Exception e, String fmt, Object...args) { 6288 final String message = String.format(fmt, args); 6289 synchronized (mLock) { 6290 mWtfHistory.log(message); 6291 } 6292 6293 if (e != null) { 6294 Slog.wtf(TAG, message, e); 6295 } else { 6296 Slog.wtf(TAG, message); 6297 } 6298 } 6299 actionAsString(int action)6300 private static String actionAsString(int action) { 6301 switch (action) { 6302 case ACTION_START_SESSION: 6303 return "START_SESSION"; 6304 case ACTION_VIEW_ENTERED: 6305 return "VIEW_ENTERED"; 6306 case ACTION_VIEW_EXITED: 6307 return "VIEW_EXITED"; 6308 case ACTION_VALUE_CHANGED: 6309 return "VALUE_CHANGED"; 6310 case ACTION_RESPONSE_EXPIRED: 6311 return "RESPONSE_EXPIRED"; 6312 default: 6313 return "UNKNOWN_" + action; 6314 } 6315 } 6316 sessionStateAsString(@essionState int sessionState)6317 private static String sessionStateAsString(@SessionState int sessionState) { 6318 switch (sessionState) { 6319 case STATE_UNKNOWN: 6320 return "STATE_UNKNOWN"; 6321 case STATE_ACTIVE: 6322 return "STATE_ACTIVE"; 6323 case STATE_FINISHED: 6324 return "STATE_FINISHED"; 6325 case STATE_REMOVED: 6326 return "STATE_REMOVED"; 6327 default: 6328 return "UNKNOWN_SESSION_STATE_" + sessionState; 6329 } 6330 } 6331 getAutofillServiceUid()6332 private int getAutofillServiceUid() { 6333 ServiceInfo serviceInfo = mService.getServiceInfo(); 6334 return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid; 6335 } 6336 6337 // FieldClassificationServiceCallbacks start onClassificationRequestSuccess(@ullable FieldClassificationResponse response)6338 public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) { 6339 mClassificationState.updateResponseReceived(response); 6340 } 6341 onClassificationRequestFailure(int requestId, @Nullable CharSequence message)6342 public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) { 6343 6344 } 6345 onClassificationRequestTimeout(int requestId)6346 public void onClassificationRequestTimeout(int requestId) { 6347 6348 } 6349 6350 @Override onServiceDied(@onNull RemoteFieldClassificationService service)6351 public void onServiceDied(@NonNull RemoteFieldClassificationService service) { 6352 Slog.w(TAG, "removing session because service died"); 6353 synchronized (mLock) { 6354 // TODO(b/266379948) 6355 // forceRemoveFromServiceLocked(); 6356 } 6357 } 6358 6359 @Override logFieldClassificationEvent( long startTime, FieldClassificationResponse response, @FieldClassificationEventLogger.FieldClassificationStatus int status)6360 public void logFieldClassificationEvent( 6361 long startTime, FieldClassificationResponse response, 6362 @FieldClassificationEventLogger.FieldClassificationStatus int status) { 6363 final FieldClassificationEventLogger logger = FieldClassificationEventLogger.createLogger(); 6364 logger.startNewLogForRequest(); 6365 logger.maybeSetLatencyMillis( 6366 SystemClock.elapsedRealtime() - startTime); 6367 logger.maybeSetAppPackageUid(uid); 6368 logger.maybeSetNextFillRequestId(mFillRequestIdSnapshot + 1); 6369 logger.maybeSetRequestId(sIdCounterForPcc.get()); 6370 logger.maybeSetSessionId(id); 6371 int count = -1; 6372 if (response != null) { 6373 count = response.getClassifications().size(); 6374 } 6375 logger.maybeSetRequestStatus(status); 6376 logger.maybeSetCountClassifications(count); 6377 logger.logAndEndEvent(); 6378 mFillRequestIdSnapshot = DEFAULT__FILL_REQUEST_ID_SNAPSHOT; 6379 } 6380 // FieldClassificationServiceCallbacks end 6381 6382 } 6383