• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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