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