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