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