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