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.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
20 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
21 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
22 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
23 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
24 import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
25 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
26 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
27 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
28 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
29 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
30 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
31 
32 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
33 import static com.android.server.autofill.Helper.containsCharsInOrder;
34 import static com.android.server.autofill.Helper.createSanitizers;
35 import static com.android.server.autofill.Helper.getNumericValue;
36 import static com.android.server.autofill.Helper.sDebug;
37 import static com.android.server.autofill.Helper.sVerbose;
38 import static com.android.server.autofill.Helper.toArray;
39 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
40 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
41 
42 import android.annotation.IntDef;
43 import android.annotation.NonNull;
44 import android.annotation.Nullable;
45 import android.app.Activity;
46 import android.app.ActivityTaskManager;
47 import android.app.IAssistDataReceiver;
48 import android.app.assist.AssistStructure;
49 import android.app.assist.AssistStructure.AutofillOverlay;
50 import android.app.assist.AssistStructure.ViewNode;
51 import android.content.ClipData;
52 import android.content.ComponentName;
53 import android.content.Context;
54 import android.content.Intent;
55 import android.content.IntentSender;
56 import android.graphics.Bitmap;
57 import android.graphics.Rect;
58 import android.graphics.drawable.Drawable;
59 import android.metrics.LogMaker;
60 import android.os.Binder;
61 import android.os.Build;
62 import android.os.Bundle;
63 import android.os.Handler;
64 import android.os.IBinder;
65 import android.os.IBinder.DeathRecipient;
66 import android.os.Parcelable;
67 import android.os.RemoteCallback;
68 import android.os.RemoteException;
69 import android.os.SystemClock;
70 import android.service.autofill.AutofillFieldClassificationService.Scores;
71 import android.service.autofill.AutofillService;
72 import android.service.autofill.CompositeUserData;
73 import android.service.autofill.Dataset;
74 import android.service.autofill.FieldClassification;
75 import android.service.autofill.FieldClassification.Match;
76 import android.service.autofill.FieldClassificationUserData;
77 import android.service.autofill.FillContext;
78 import android.service.autofill.FillEventHistory.Event;
79 import android.service.autofill.FillEventHistory.Event.NoSaveReason;
80 import android.service.autofill.FillRequest;
81 import android.service.autofill.FillResponse;
82 import android.service.autofill.InlinePresentation;
83 import android.service.autofill.InternalSanitizer;
84 import android.service.autofill.InternalValidator;
85 import android.service.autofill.SaveInfo;
86 import android.service.autofill.SaveRequest;
87 import android.service.autofill.UserData;
88 import android.service.autofill.ValueFinder;
89 import android.text.TextUtils;
90 import android.util.ArrayMap;
91 import android.util.ArraySet;
92 import android.util.LocalLog;
93 import android.util.Log;
94 import android.util.Pair;
95 import android.util.Slog;
96 import android.util.SparseArray;
97 import android.util.TimeUtils;
98 import android.view.KeyEvent;
99 import android.view.accessibility.AccessibilityManager;
100 import android.view.autofill.AutofillId;
101 import android.view.autofill.AutofillManager;
102 import android.view.autofill.AutofillManager.SmartSuggestionMode;
103 import android.view.autofill.AutofillValue;
104 import android.view.autofill.IAutoFillManagerClient;
105 import android.view.autofill.IAutofillWindowPresenter;
106 import android.view.inputmethod.InlineSuggestionsRequest;
107 
108 import com.android.internal.R;
109 import com.android.internal.annotations.GuardedBy;
110 import com.android.internal.logging.MetricsLogger;
111 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
112 import com.android.internal.util.ArrayUtils;
113 import com.android.server.autofill.ui.AutoFillUI;
114 import com.android.server.autofill.ui.InlineFillUi;
115 import com.android.server.autofill.ui.PendingUi;
116 import com.android.server.inputmethod.InputMethodManagerInternal;
117 
118 import java.io.PrintWriter;
119 import java.lang.annotation.Retention;
120 import java.lang.annotation.RetentionPolicy;
121 import java.util.ArrayList;
122 import java.util.Arrays;
123 import java.util.Collection;
124 import java.util.Collections;
125 import java.util.List;
126 import java.util.Objects;
127 import java.util.Optional;
128 import java.util.concurrent.atomic.AtomicInteger;
129 import java.util.function.Consumer;
130 import java.util.function.Function;
131 
132 /**
133  * A session for a given activity.
134  *
135  * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
136  * of the current {@link ViewState} to display the appropriate UI.
137  *
138  * <p>Although the autofill requests and callbacks are stateless from the service's point of
139  * view, we need to keep state in the framework side for cases such as authentication. For
140  * example, when service return a {@link FillResponse} that contains all the fields needed
141  * to fill the activity but it requires authentication first, that response need to be held
142  * until the user authenticates or it times out.
143  */
144 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
145         AutoFillUI.AutoFillUiCallback, ValueFinder {
146     private static final String TAG = "AutofillSession";
147 
148     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
149 
150     private final AutofillManagerServiceImpl mService;
151     private final Handler mHandler;
152     private final Object mLock;
153     private final AutoFillUI mUi;
154 
155     private final MetricsLogger mMetricsLogger = new MetricsLogger();
156 
157     static final int AUGMENTED_AUTOFILL_REQUEST_ID = 1;
158 
159     private static AtomicInteger sIdCounter = new AtomicInteger(2);
160 
161     @GuardedBy("mLock")
162     private @SessionState int mSessionState = STATE_UNKNOWN;
163 
164     /** Session state uninitiated. */
165     public static final int STATE_UNKNOWN = 0;
166 
167     /** Session is active for filling. */
168     public static final int STATE_ACTIVE = 1;
169 
170     /** Session finished for filling, staying alive for saving. */
171     public static final int STATE_FINISHED = 2;
172 
173     /** Session is destroyed and removed from the manager service. */
174     public static final int STATE_REMOVED = 3;
175 
176     @IntDef(prefix = { "STATE_" }, value = {
177             STATE_UNKNOWN,
178             STATE_ACTIVE,
179             STATE_FINISHED,
180             STATE_REMOVED
181     })
182     @Retention(RetentionPolicy.SOURCE)
183     @interface SessionState{}
184 
185     @GuardedBy("mLock")
186     private final SessionFlags mSessionFlags;
187 
188     /**
189      * ID of the session.
190      *
191      * <p>It's always a positive number, to make it easier to embed it in a long.
192      */
193     public final int id;
194 
195     /** userId the session belongs to */
196     public final int userId;
197 
198     /** The uid of the app that's being autofilled */
199     public final int uid;
200 
201     /** ID of the task associated with this session's activity */
202     public final int taskId;
203 
204     /** Flags used to start the session */
205     public final int mFlags;
206 
207     @GuardedBy("mLock")
208     @NonNull private IBinder mActivityToken;
209 
210     /** The app activity that's being autofilled */
211     @NonNull private final ComponentName mComponentName;
212 
213     /** Whether the app being autofilled is running in compat mode. */
214     private final boolean mCompatMode;
215 
216     /** Node representing the URL bar on compat mode. */
217     @GuardedBy("mLock")
218     private ViewNode mUrlBar;
219 
220     @GuardedBy("mLock")
221     private boolean mSaveOnAllViewsInvisible;
222 
223     @GuardedBy("mLock")
224     private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
225 
226     /**
227      * Tracks the most recent IME inline request and the corresponding request id, for regular
228      * autofill.
229      */
230     @GuardedBy("mLock")
231     @Nullable private Pair<Integer, InlineSuggestionsRequest> mLastInlineSuggestionsRequest;
232 
233     /**
234      * Id of the View currently being displayed.
235      */
236     @GuardedBy("mLock")
237     @Nullable private AutofillId mCurrentViewId;
238 
239     @GuardedBy("mLock")
240     private IAutoFillManagerClient mClient;
241 
242     @GuardedBy("mLock")
243     private DeathRecipient mClientVulture;
244 
245     /**
246      * Reference to the remote service.
247      *
248      * <p>Only {@code null} when the session is for augmented autofill only.
249      */
250     @Nullable
251     private final RemoteFillService mRemoteFillService;
252 
253     @GuardedBy("mLock")
254     private SparseArray<FillResponse> mResponses;
255 
256     /**
257      * Contexts read from the app; they will be updated (sanitized, change values for save) before
258      * sent to {@link AutofillService}. Ordered by the time they were read.
259      */
260     @GuardedBy("mLock")
261     private ArrayList<FillContext> mContexts;
262 
263     /**
264      * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
265      */
266     private boolean mHasCallback;
267 
268     /**
269      * Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is
270      * saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
271      */
272     @GuardedBy("mLock")
273     private Bundle mClientState;
274 
275     @GuardedBy("mLock")
276     private boolean mDestroyed;
277 
278     /**
279      * Helper used to handle state of Save UI when it must be hiding to show a custom description
280      * link and later recovered.
281      */
282     @GuardedBy("mLock")
283     private PendingUi mPendingSaveUi;
284 
285     /**
286      * List of dataset ids selected by the user.
287      */
288     @GuardedBy("mLock")
289     private ArrayList<String> mSelectedDatasetIds;
290 
291     /**
292      * When the session started (using elapsed time since boot).
293      */
294     private final long mStartTime;
295 
296     /**
297      * When the UI was shown for the first time (using elapsed time since boot).
298      */
299     @GuardedBy("mLock")
300     private long mUiShownTime;
301 
302     @GuardedBy("mLock")
303     private final LocalLog mUiLatencyHistory;
304 
305     @GuardedBy("mLock")
306     private final LocalLog mWtfHistory;
307 
308     /**
309      * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
310      */
311     @GuardedBy("mLock")
312     private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1);
313 
314     /**
315      * Destroys the augmented Autofill UI.
316      */
317     // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the
318     // main reason being the cases where user tap HOME.
319     // Right now it's completely destroying the UI, but we need to decide whether / how to
320     // properly recover it later (for example, if the user switches back to the activity,
321     // should it be restored? Right now it kind of is, because Autofill's Session trigger a
322     // new FillRequest, which in turn triggers the Augmented Autofill request again)
323     @GuardedBy("mLock")
324     @Nullable
325     private Runnable mAugmentedAutofillDestroyer;
326 
327     /**
328      * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics.
329      */
330     @GuardedBy("mLock")
331     private ArrayList<LogMaker> mAugmentedRequestsLogs;
332 
333 
334     /**
335      * List of autofill ids of autofillable fields present in the AssistStructure that can be used
336      * to trigger new augmented autofill requests (because the "standard" service was not interested
337      * on autofilling the app.
338      */
339     @GuardedBy("mLock")
340     private ArrayList<AutofillId> mAugmentedAutofillableIds;
341 
342     @NonNull
343     private final AutofillInlineSessionController mInlineSessionController;
344 
345     /**
346      * Receiver of assist data from the app's {@link Activity}.
347      */
348     private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl();
349 
350     private final AccessibilityManager mAccessibilityManager;
351 
onSwitchInputMethodLocked()352     void onSwitchInputMethodLocked() {
353         // One caveat is that for the case where the focus is on a field for which regular autofill
354         // returns null, and augmented autofill is triggered,  and then the user switches the input
355         // method. Tapping on the field again will not trigger a new augmented autofill request.
356         // This may be fixed by adding more checks such as whether mCurrentViewId is null.
357         if (mSessionFlags.mExpiredResponse) {
358             return;
359         }
360         if (shouldResetSessionStateOnInputMethodSwitch()) {
361             // Set the old response expired, so the next action (ACTION_VIEW_ENTERED) can trigger
362             // a new fill request.
363             mSessionFlags.mExpiredResponse = true;
364             // Clear the augmented autofillable ids so augmented autofill will trigger again.
365             mAugmentedAutofillableIds = null;
366             // In case the field is augmented autofill only, we clear the current view id, so that
367             // we won't skip view entered due to same view entered, for the augmented autofill.
368             if (mSessionFlags.mAugmentedAutofillOnly) {
369                 mCurrentViewId = null;
370             }
371         }
372     }
373 
shouldResetSessionStateOnInputMethodSwitch()374     private boolean shouldResetSessionStateOnInputMethodSwitch() {
375         // One of below cases will need a new fill request to update the inline spec for the new
376         // input method.
377         // 1. The autofill provider supports inline suggestion and the render service is available.
378         // 2. Had triggered the augmented autofill and the render service is available. Whether the
379         // augmented autofill triggered by:
380         //    a. Augmented autofill only
381         //    b. The autofill provider respond null
382         if (mService.getRemoteInlineSuggestionRenderServiceLocked() == null) {
383             return false;
384         }
385 
386         if (mSessionFlags.mInlineSupportedByService) {
387             return true;
388         }
389 
390         final ViewState state = mViewStates.get(mCurrentViewId);
391         if (state != null
392                 && (state.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
393             return true;
394         }
395 
396         return false;
397     }
398 
399     /**
400      * Collection of flags/booleans that helps determine Session behaviors.
401      */
402     private final class SessionFlags {
403         /** Whether autofill is disabled by the service */
404         @GuardedBy("mLock")
405         private boolean mAutofillDisabled;
406 
407         /** Whether the autofill service supports inline suggestions */
408         @GuardedBy("mLock")
409         private boolean mInlineSupportedByService;
410 
411         /** True if session is for augmented only */
412         @GuardedBy("mLock")
413         private boolean mAugmentedAutofillOnly;
414 
415         /** Whether the session is currently showing the SaveUi. */
416         @GuardedBy("mLock")
417         private boolean mShowingSaveUi;
418 
419         /** Whether the current {@link FillResponse} is expired. */
420         @GuardedBy("mLock")
421         private boolean mExpiredResponse;
422     }
423 
424     /**
425      * TODO(b/151867668): improve how asynchronous data dependencies are handled, without using
426      * CountDownLatch.
427      */
428     private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
429         @GuardedBy("mLock")
430         private boolean mWaitForInlineRequest;
431         @GuardedBy("mLock")
432         private InlineSuggestionsRequest mPendingInlineSuggestionsRequest;
433         @GuardedBy("mLock")
434         private FillRequest mPendingFillRequest;
435 
newAutofillRequestLocked(ViewState viewState, boolean isInlineRequest)436         @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState,
437                 boolean isInlineRequest) {
438             mPendingFillRequest = null;
439             mWaitForInlineRequest = isInlineRequest;
440             mPendingInlineSuggestionsRequest = null;
441             return isInlineRequest ? (inlineSuggestionsRequest) -> {
442                 synchronized (mLock) {
443                     if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
444                         return;
445                     }
446                     mWaitForInlineRequest = inlineSuggestionsRequest != null;
447                     mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
448                     maybeRequestFillLocked();
449                     viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
450                 }
451             } : null;
452         }
453 
454         void maybeRequestFillLocked() {
455             if (mPendingFillRequest == null) {
456                 return;
457             }
458 
459             if (mWaitForInlineRequest) {
460                 if (mPendingInlineSuggestionsRequest == null) {
461                     return;
462                 }
463 
464                 // If a11y touch exploration is enabled, then we do not send an inline fill request
465                 // to the regular af service, because dropdown UI is easier to use.
466                 if (!mAccessibilityManager.isTouchExplorationEnabled()) {
467                     mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
468                             mPendingFillRequest.getFillContexts(),
469                             mPendingFillRequest.getClientState(),
470                             mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
471                 }
472             }
473 
474             mRemoteFillService.onFillRequest(mPendingFillRequest);
475             mPendingInlineSuggestionsRequest = null;
476             mWaitForInlineRequest = false;
477             mPendingFillRequest = null;
478         }
479 
480         @Override
481         public void onHandleAssistData(Bundle resultData) throws RemoteException {
482             if (mRemoteFillService == null) {
483                 wtf(null, "onHandleAssistData() called without a remote service. "
484                         + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
485                 return;
486             }
487             // Keeps to prevent it is cleared on multiple threads.
488             final AutofillId currentViewId = mCurrentViewId;
489             if (currentViewId == null) {
490                 Slog.w(TAG, "No current view id - session might have finished");
491                 return;
492             }
493 
494             final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
495             if (structure == null) {
496                 Slog.e(TAG, "No assist structure - app might have crashed providing it");
497                 return;
498             }
499 
500             final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
501             if (receiverExtras == null) {
502                 Slog.e(TAG, "No receiver extras - app might have crashed providing it");
503                 return;
504             }
505 
506             final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
507 
508             if (sVerbose) {
509                 Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure);
510             }
511 
512             final FillRequest request;
513             synchronized (mLock) {
514                 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(),
515                 // even if if the activity is gone by then, but structure .ensureData() gives a
516                 // ONE_WAY warning because system_service could block on app calls. We need to
517                 // change AssistStructure so it provides a "one-way" writeToParcel() method that
518                 // sends all the data
519                 try {
520                     structure.ensureDataForAutofill();
521                 } catch (RuntimeException e) {
522                     wtf(e, "Exception lazy loading assist structure for %s: %s",
523                             structure.getActivityComponent(), e);
524                     return;
525                 }
526 
527                 final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure,
528                         /* autofillableOnly= */false);
529                 for (int i = 0; i < ids.size(); i++) {
530                     ids.get(i).setSessionId(Session.this.id);
531                 }
532 
533                 // Flags used to start the session.
534                 int flags = structure.getFlags();
535 
536                 if (mCompatMode) {
537                     // Sanitize URL bar, if needed
538                     final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode(
539                             mComponentName.getPackageName());
540                     if (sDebug) {
541                         Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds));
542                     }
543                     if (urlBarIds != null) {
544                         mUrlBar = Helper.sanitizeUrlBar(structure, urlBarIds);
545                         if (mUrlBar != null) {
546                             final AutofillId urlBarId = mUrlBar.getAutofillId();
547                             if (sDebug) {
548                                 Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain "
549                                         + mUrlBar.getWebDomain());
550                             }
551                             final ViewState viewState = new ViewState(urlBarId, Session.this,
552                                     ViewState.STATE_URL_BAR);
553                             mViewStates.put(urlBarId, viewState);
554                         }
555                     }
556                     flags |= FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST;
557                 }
558                 structure.sanitizeForParceling(true);
559 
560                 if (mContexts == null) {
561                     mContexts = new ArrayList<>(1);
562                 }
563                 mContexts.add(new FillContext(requestId, structure, currentViewId));
564 
565                 cancelCurrentRequestLocked();
566 
567                 final int numContexts = mContexts.size();
568                 for (int i = 0; i < numContexts; i++) {
569                     fillContextWithAllowedValuesLocked(mContexts.get(i), flags);
570                 }
571 
572                 final ArrayList<FillContext> contexts =
573                         mergePreviousSessionLocked(/* forSave= */ false);
574                 request = new FillRequest(requestId, contexts, mClientState, flags,
575                         /*inlineSuggestionsRequest=*/null);
576 
577                 mPendingFillRequest = request;
578                 maybeRequestFillLocked();
579             }
580 
581             if (mActivityToken != null) {
582                 mService.sendActivityAssistDataToContentCapture(mActivityToken, resultData);
583             }
584         }
585 
586         @Override
587         public void onHandleAssistScreenshot(Bitmap screenshot) {
588             // Do nothing
589         }
590     };
591 
592     /**
593      * Returns the ids of all entries in {@link #mViewStates} in the same order.
594      */
595     @GuardedBy("mLock")
596     private AutofillId[] getIdsOfAllViewStatesLocked() {
597         final int numViewState = mViewStates.size();
598         final AutofillId[] ids = new AutofillId[numViewState];
599         for (int i = 0; i < numViewState; i++) {
600             ids[i] = mViewStates.valueAt(i).id;
601         }
602 
603         return ids;
604     }
605 
606     /**
607      * Returns the String value of an {@link AutofillValue} by {@link AutofillId id} if it is of
608      * type {@code AUTOFILL_TYPE_TEXT} or {@code AUTOFILL_TYPE_LIST}.
609      */
610     @Override
611     @Nullable
612     public String findByAutofillId(@NonNull AutofillId id) {
613         synchronized (mLock) {
614             AutofillValue value = findValueLocked(id);
615             if (value != null) {
616                 if (value.isText()) {
617                     return value.getTextValue().toString();
618                 }
619 
620                 if (value.isList()) {
621                     final CharSequence[] options = getAutofillOptionsFromContextsLocked(id);
622                     if (options != null) {
623                         final int index = value.getListValue();
624                         final CharSequence option = options[index];
625                         return option != null ? option.toString() : null;
626                     } else {
627                         Slog.w(TAG, "findByAutofillId(): no autofill options for id " + id);
628                     }
629                 }
630             }
631         }
632         return null;
633     }
634 
635     @Override
636     public AutofillValue findRawValueByAutofillId(AutofillId id) {
637         synchronized (mLock) {
638             return findValueLocked(id);
639         }
640     }
641 
642     /**
643      * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
644      * or {@code null} when not found on either of them.
645      */
646     @GuardedBy("mLock")
647     @Nullable
648     private AutofillValue findValueLocked(@NonNull AutofillId autofillId) {
649         final AutofillValue value = findValueFromThisSessionOnlyLocked(autofillId);
650         if (value != null) {
651             return getSanitizedValue(createSanitizers(getSaveInfoLocked()), autofillId, value);
652         }
653 
654         // TODO(b/113281366): rather than explicitly look for previous session, it might be better
655         // to merge the sessions when created (see note on mergePreviousSessionLocked())
656         final ArrayList<Session> previousSessions = mService.getPreviousSessionsLocked(this);
657         if (previousSessions != null) {
658             if (sDebug) {
659                 Slog.d(TAG, "findValueLocked(): looking on " + previousSessions.size()
660                         + " previous sessions for autofillId " + autofillId);
661             }
662             for (int i = 0; i < previousSessions.size(); i++) {
663                 final Session previousSession = previousSessions.get(i);
664                 final AutofillValue previousValue = previousSession
665                         .findValueFromThisSessionOnlyLocked(autofillId);
666                 if (previousValue != null) {
667                     return getSanitizedValue(createSanitizers(previousSession.getSaveInfoLocked()),
668                             autofillId, previousValue);
669                 }
670             }
671         }
672         return null;
673     }
674 
675     @Nullable
676     private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) {
677         final ViewState state = mViewStates.get(autofillId);
678         if (state == null) {
679             if (sDebug) Slog.d(TAG, "findValueLocked(): no view state for " + autofillId);
680             return null;
681         }
682         AutofillValue value = state.getCurrentValue();
683         if (value == null) {
684             if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId);
685             value = getValueFromContextsLocked(autofillId);
686         }
687         return value;
688     }
689 
690     /**
691      * Updates values of the nodes in the context's structure so that:
692      *
693      * - proper node is focused
694      * - autofillValue is sent back to service when it was previously autofilled
695      * - autofillValue is sent in the view used to force a request
696      *
697      * @param fillContext The context to be filled
698      * @param flags The flags that started the session
699      */
700     @GuardedBy("mLock")
701     private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
702         final ViewNode[] nodes = fillContext
703                 .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
704 
705         final int numViewState = mViewStates.size();
706         for (int i = 0; i < numViewState; i++) {
707             final ViewState viewState = mViewStates.valueAt(i);
708 
709             final ViewNode node = nodes[i];
710             if (node == null) {
711                 if (sVerbose) {
712                     Slog.v(TAG,
713                             "fillContextWithAllowedValuesLocked(): no node for " + viewState.id);
714                 }
715                 continue;
716             }
717 
718             final AutofillValue currentValue = viewState.getCurrentValue();
719             final AutofillValue filledValue = viewState.getAutofilledValue();
720             final AutofillOverlay overlay = new AutofillOverlay();
721 
722             // Sanitizes the value if the current value matches what the service sent.
723             if (filledValue != null && filledValue.equals(currentValue)) {
724                 overlay.value = currentValue;
725             }
726 
727             if (mCurrentViewId != null) {
728                 // Updates the focus value.
729                 overlay.focused = mCurrentViewId.equals(viewState.id);
730                 // Sanitizes the value of the focused field in a manual request.
731                 if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) {
732                     overlay.value = currentValue;
733                 }
734             }
735             node.setAutofillOverlay(overlay);
736         }
737     }
738 
739     /**
740      * Cancels the last request sent to the {@link #mRemoteFillService}.
741      */
742     @GuardedBy("mLock")
743     private void cancelCurrentRequestLocked() {
744         if (mRemoteFillService == null) {
745             wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
746                     + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
747             return;
748         }
749         final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
750 
751         // Remove the FillContext as there will never be a response for the service
752         if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
753             final int numContexts = mContexts.size();
754 
755             // It is most likely the last context, hence search backwards
756             for (int i = numContexts - 1; i >= 0; i--) {
757                 if (mContexts.get(i).getRequestId() == canceledRequest) {
758                     if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
759                     mContexts.remove(i);
760                     break;
761                 }
762             }
763         }
764     }
765 
766     private boolean isViewFocusedLocked(int flags) {
767         return (flags & FLAG_VIEW_NOT_FOCUSED) == 0;
768     }
769 
770     /**
771      * Clears the existing response for the partition, reads a new structure, and then requests a
772      * new fill response from the fill service.
773      *
774      * <p> Also asks the IME to make an inline suggestions request if it's enabled.
775      */
776     @GuardedBy("mLock")
777     private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
778             int flags) {
779         final FillResponse existingResponse = viewState.getResponse();
780         if (existingResponse != null) {
781             setViewStatesLocked(
782                     existingResponse,
783                     ViewState.STATE_INITIAL,
784                     /* clearResponse= */ true);
785         }
786         mSessionFlags.mExpiredResponse = false;
787         mSessionState = STATE_ACTIVE;
788         if (mSessionFlags.mAugmentedAutofillOnly || mRemoteFillService == null) {
789             if (sVerbose) {
790                 Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead "
791                         + "(mForAugmentedAutofillOnly=" + mSessionFlags.mAugmentedAutofillOnly
792                         + ", flags=" + flags + ")");
793             }
794             mSessionFlags.mAugmentedAutofillOnly = true;
795             triggerAugmentedAutofillLocked(flags);
796             return;
797         }
798 
799         viewState.setState(newState);
800 
801         int requestId;
802         // TODO(b/158623971): Update this to prevent possible overflow
803         do {
804             requestId = sIdCounter.getAndIncrement();
805         } while (requestId == INVALID_REQUEST_ID);
806 
807         // Create a metrics log for the request
808         final int ordinal = mRequestLogs.size() + 1;
809         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST)
810                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal);
811         if (flags != 0) {
812             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags);
813         }
814         mRequestLogs.put(requestId, log);
815 
816         if (sVerbose) {
817             Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId
818                     + ", flags=" + flags);
819         }
820 
821         // If the focus changes very quickly before the first request is returned each focus change
822         // triggers a new partition and we end up with many duplicate partitions. This is
823         // enhanced as the focus change can be much faster than the taking of the assist structure.
824         // Hence remove the currently queued request and replace it with the one queued after the
825         // structure is taken. This causes only one fill request per burst of focus changes.
826         cancelCurrentRequestLocked();
827 
828         // Only ask IME to create inline suggestions request if Autofill provider supports it and
829         // the render service is available except the autofill is triggered manually and the view
830         // is also not focused.
831         final RemoteInlineSuggestionRenderService remoteRenderService =
832                 mService.getRemoteInlineSuggestionRenderServiceLocked();
833         if (mSessionFlags.mInlineSupportedByService
834                 && remoteRenderService != null
835                 && isViewFocusedLocked(flags)) {
836             Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
837                     mAssistReceiver.newAutofillRequestLocked(viewState,
838                             /* isInlineRequest= */ true);
839             if (inlineSuggestionsRequestConsumer != null) {
840                 final AutofillId focusedId = mCurrentViewId;
841                 final int requestIdCopy = requestId;
842                 remoteRenderService.getInlineSuggestionsRendererInfo(
843                         new RemoteCallback((extras) -> {
844                             synchronized (mLock) {
845                                 mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
846                                         focusedId, inlineSuggestionsRequestCacheDecorator(
847                                                 inlineSuggestionsRequestConsumer, requestIdCopy),
848                                         extras);
849                             }
850                         }, mHandler)
851                 );
852                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
853             }
854         } else {
855             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
856         }
857 
858         // Now request the assist structure data.
859         try {
860             final Bundle receiverExtras = new Bundle();
861             receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
862             final long identity = Binder.clearCallingIdentity();
863             try {
864                 if (!ActivityTaskManager.getService().requestAutofillData(mAssistReceiver,
865                         receiverExtras, mActivityToken, flags)) {
866                     Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
867                 }
868             } finally {
869                 Binder.restoreCallingIdentity(identity);
870             }
871         } catch (RemoteException e) {
872             // Should not happen, it's a local call.
873         }
874     }
875 
876     Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
877             @NonNull Context context, @NonNull Handler handler, int userId, @NonNull Object lock,
878             int sessionId, int taskId, int uid, @NonNull IBinder activityToken,
879             @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
880             @NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName,
881             @NonNull ComponentName componentName, boolean compatMode,
882             boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags,
883             @NonNull InputMethodManagerInternal inputMethodManagerInternal) {
884         if (sessionId < 0) {
885             wtf(null, "Non-positive sessionId: %s", sessionId);
886         }
887         id = sessionId;
888         mFlags = flags;
889         this.userId = userId;
890         this.taskId = taskId;
891         this.uid = uid;
892         mStartTime = SystemClock.elapsedRealtime();
893         mService = service;
894         mLock = lock;
895         mUi = ui;
896         mHandler = handler;
897         mRemoteFillService = serviceComponentName == null ? null
898                 : new RemoteFillService(context, serviceComponentName, userId, this,
899                         bindInstantServiceAllowed);
900         mAccessibilityManager = AccessibilityManager.getInstance(context);
901         mActivityToken = activityToken;
902         mHasCallback = hasCallback;
903         mUiLatencyHistory = uiLatencyHistory;
904         mWtfHistory = wtfHistory;
905         mComponentName = componentName;
906         mCompatMode = compatMode;
907         mSessionState = STATE_ACTIVE;
908         synchronized (mLock) {
909             mSessionFlags = new SessionFlags();
910             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
911             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
912             setClientLocked(client);
913         }
914 
915         mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
916                 userId, componentName, handler, mLock,
917                 new InlineFillUi.InlineUiEventCallback() {
918                     @Override
919                     public void notifyInlineUiShown(AutofillId autofillId) {
920                         notifyFillUiShown(autofillId);
921                     }
922 
923                     @Override
924                     public void notifyInlineUiHidden(AutofillId autofillId) {
925                         notifyFillUiHidden(autofillId);
926                     }
927                 });
928 
929         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
930                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
931     }
932 
933     /**
934      * Gets the currently registered activity token
935      *
936      * @return The activity token
937      */
938     @GuardedBy("mLock")
939     @NonNull IBinder getActivityTokenLocked() {
940         return mActivityToken;
941     }
942 
943     /**
944      * Sets new activity and client for this session.
945      *
946      * @param newActivity The token of the new activity
947      * @param newClient The client receiving autofill callbacks
948      */
949     void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) {
950         synchronized (mLock) {
951             if (mDestroyed) {
952                 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: "
953                         + id + " destroyed");
954                 return;
955             }
956             mActivityToken = newActivity;
957             setClientLocked(newClient);
958 
959             // The tracked id are not persisted in the client, hence update them
960             updateTrackedIdsLocked();
961         }
962     }
963 
964     @GuardedBy("mLock")
965     private void setClientLocked(@NonNull IBinder client) {
966         unlinkClientVultureLocked();
967         mClient = IAutoFillManagerClient.Stub.asInterface(client);
968         mClientVulture = () -> {
969             synchronized (mLock) {
970                 Slog.d(TAG, "handling death of " + mActivityToken + " when saving="
971                         + mSessionFlags.mShowingSaveUi);
972                 if (mSessionFlags.mShowingSaveUi) {
973                     mUi.hideFillUi(this);
974                 } else {
975                     mUi.destroyAll(mPendingSaveUi, this, false);
976                 }
977             }
978         };
979         try {
980             mClient.asBinder().linkToDeath(mClientVulture, 0);
981         } catch (RemoteException e) {
982             Slog.w(TAG, "could not set binder death listener on autofill client: " + e);
983             mClientVulture = null;
984         }
985     }
986 
987     @GuardedBy("mLock")
988     private void unlinkClientVultureLocked() {
989         if (mClient != null && mClientVulture != null) {
990             final boolean unlinked = mClient.asBinder().unlinkToDeath(mClientVulture, 0);
991             if (!unlinked) {
992                 Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken);
993             }
994             mClientVulture = null;
995         }
996     }
997 
998     // FillServiceCallbacks
999     @Override
1000     public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
1001             @NonNull String servicePackageName, int requestFlags) {
1002         final AutofillId[] fieldClassificationIds;
1003 
1004         final LogMaker requestLog;
1005 
1006         synchronized (mLock) {
1007             if (mDestroyed) {
1008                 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
1009                         + id + " destroyed");
1010                 return;
1011             }
1012 
1013             requestLog = mRequestLogs.get(requestId);
1014             if (requestLog != null) {
1015                 requestLog.setType(MetricsEvent.TYPE_SUCCESS);
1016             } else {
1017                 Slog.w(TAG, "onFillRequestSuccess(): no request log for id " + requestId);
1018             }
1019             if (response == null) {
1020                 if (requestLog != null) {
1021                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
1022                 }
1023                 processNullResponseLocked(requestId, requestFlags);
1024                 return;
1025             }
1026 
1027             fieldClassificationIds = response.getFieldClassificationIds();
1028             if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
1029                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
1030                 processNullResponseLocked(requestId, requestFlags);
1031                 return;
1032             }
1033         }
1034 
1035         mService.setLastResponse(id, response);
1036 
1037         final long disableDuration = response.getDisableDuration();
1038         final boolean autofillDisabled = disableDuration > 0;
1039         if (autofillDisabled) {
1040             final int flags = response.getFlags();
1041             final boolean disableActivityOnly =
1042                     (flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0;
1043             notifyDisableAutofillToClient(disableDuration,
1044                     disableActivityOnly ? mComponentName : null);
1045 
1046             if (disableActivityOnly) {
1047                 mService.disableAutofillForActivity(mComponentName, disableDuration,
1048                         id, mCompatMode);
1049             } else {
1050                 mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
1051                         id, mCompatMode);
1052             }
1053 
1054             synchronized (mLock) {
1055                 mSessionFlags.mAutofillDisabled = true;
1056 
1057                 // Although "standard" autofill is disabled, it might still trigger augmented
1058                 // autofill
1059                 if (triggerAugmentedAutofillLocked(requestFlags) != null) {
1060                     mSessionFlags.mAugmentedAutofillOnly = true;
1061                     if (sDebug) {
1062                         Slog.d(TAG, "Service disabled autofill for " + mComponentName
1063                                 + ", but session is kept for augmented autofill only");
1064                     }
1065                     return;
1066                 }
1067             }
1068 
1069             if (sDebug) {
1070                 final StringBuilder message = new StringBuilder("Service disabled autofill for ")
1071                                 .append(mComponentName)
1072                                 .append(": flags=").append(flags)
1073                                 .append(", duration=");
1074                 TimeUtils.formatDuration(disableDuration, message);
1075                 Slog.d(TAG, message.toString());
1076             }
1077         }
1078 
1079         if (((response.getDatasets() == null || response.getDatasets().isEmpty())
1080                         && response.getAuthentication() == null)
1081                 || autofillDisabled) {
1082             // Response is "empty" from an UI point of view, need to notify client.
1083             notifyUnavailableToClient(
1084                     autofillDisabled ? AutofillManager.STATE_DISABLED_BY_SERVICE : 0,
1085                     /* autofillableIds= */ null);
1086             synchronized (mLock) {
1087                 mInlineSessionController.setInlineFillUiLocked(
1088                         InlineFillUi.emptyUi(mCurrentViewId));
1089             }
1090         }
1091 
1092         if (requestLog != null) {
1093             requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
1094                             response.getDatasets() == null ? 0 : response.getDatasets().size());
1095             if (fieldClassificationIds != null) {
1096                 requestLog.addTaggedData(
1097                         MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS,
1098                         fieldClassificationIds.length);
1099             }
1100         }
1101 
1102         synchronized (mLock) {
1103             processResponseLocked(response, null, requestFlags);
1104         }
1105     }
1106 
1107     // FillServiceCallbacks
1108     @Override
1109     public void onFillRequestFailure(int requestId, @Nullable CharSequence message) {
1110         onFillRequestFailureOrTimeout(requestId, false, message);
1111     }
1112 
1113     // FillServiceCallbacks
1114     @Override
1115     public void onFillRequestTimeout(int requestId) {
1116         onFillRequestFailureOrTimeout(requestId, true, null);
1117     }
1118 
1119     private void onFillRequestFailureOrTimeout(int requestId, boolean timedOut,
1120             @Nullable CharSequence message) {
1121         boolean showMessage = !TextUtils.isEmpty(message);
1122         synchronized (mLock) {
1123             if (mDestroyed) {
1124                 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
1125                         + ") rejected - session: " + id + " destroyed");
1126                 return;
1127             }
1128             if (sDebug) {
1129                 Slog.d(TAG, "finishing session due to service "
1130                         + (timedOut ? "timeout" : "failure"));
1131             }
1132             mService.resetLastResponse();
1133             final LogMaker requestLog = mRequestLogs.get(requestId);
1134             if (requestLog == null) {
1135                 Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId);
1136             } else {
1137                 requestLog.setType(timedOut ? MetricsEvent.TYPE_CLOSE : MetricsEvent.TYPE_FAILURE);
1138             }
1139             if (showMessage) {
1140                 final int targetSdk = mService.getTargedSdkLocked();
1141                 if (targetSdk >= Build.VERSION_CODES.Q) {
1142                     showMessage = false;
1143                     Slog.w(TAG, "onFillRequestFailureOrTimeout(): not showing '" + message
1144                             + "' because service's targetting API " + targetSdk);
1145                 }
1146                 if (message != null) {
1147                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN,
1148                             message.length());
1149                 }
1150             }
1151         }
1152         notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED,
1153                 /* autofillableIds= */ null);
1154         if (showMessage) {
1155             getUiForShowing().showError(message, this);
1156         }
1157         removeFromService();
1158     }
1159 
1160     // FillServiceCallbacks
1161     @Override
1162     public void onSaveRequestSuccess(@NonNull String servicePackageName,
1163             @Nullable IntentSender intentSender) {
1164         synchronized (mLock) {
1165             mSessionFlags.mShowingSaveUi = false;
1166 
1167             if (mDestroyed) {
1168                 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
1169                         + id + " destroyed");
1170                 return;
1171             }
1172         }
1173         LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
1174                 .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
1175         mMetricsLogger.write(log);
1176         if (intentSender != null) {
1177             if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
1178             startIntentSenderAndFinishSession(intentSender);
1179         }
1180 
1181         // Nothing left to do...
1182         removeFromService();
1183     }
1184 
1185     // FillServiceCallbacks
1186     @Override
1187     public void onSaveRequestFailure(@Nullable CharSequence message,
1188             @NonNull String servicePackageName) {
1189         boolean showMessage = !TextUtils.isEmpty(message);
1190         synchronized (mLock) {
1191             mSessionFlags.mShowingSaveUi = false;
1192 
1193             if (mDestroyed) {
1194                 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
1195                         + id + " destroyed");
1196                 return;
1197             }
1198             if (showMessage) {
1199                 final int targetSdk = mService.getTargedSdkLocked();
1200                 if (targetSdk >= Build.VERSION_CODES.Q) {
1201                     showMessage = false;
1202                     Slog.w(TAG, "onSaveRequestFailure(): not showing '" + message
1203                             + "' because service's targetting API " + targetSdk);
1204                 }
1205             }
1206         }
1207         final LogMaker log =
1208                 newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
1209                 .setType(MetricsEvent.TYPE_FAILURE);
1210         if (message != null) {
1211             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length());
1212         }
1213         mMetricsLogger.write(log);
1214 
1215         if (showMessage) {
1216             getUiForShowing().showError(message, this);
1217         }
1218         removeFromService();
1219     }
1220 
1221     /**
1222      * Gets the {@link FillContext} for a request.
1223      *
1224      * @param requestId The id of the request
1225      *
1226      * @return The context or {@code null} if there is no context
1227      */
1228     @GuardedBy("mLock")
1229     @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
1230         if (mContexts == null) {
1231             return null;
1232         }
1233 
1234         int numContexts = mContexts.size();
1235         for (int i = 0; i < numContexts; i++) {
1236             FillContext context = mContexts.get(i);
1237 
1238             if (context.getRequestId() == requestId) {
1239                 return context;
1240             }
1241         }
1242 
1243         return null;
1244     }
1245 
1246     // VultureCallback
1247     @Override
1248     public void onServiceDied(@NonNull RemoteFillService service) {
1249         Slog.w(TAG, "removing session because service died");
1250         synchronized (mLock) {
1251             forceRemoveFromServiceLocked();
1252         }
1253     }
1254 
1255     // AutoFillUiCallback
1256     @Override
1257     public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras,
1258             boolean authenticateInline) {
1259         if (sDebug) {
1260             Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
1261                     + "; intentSender=" + intent);
1262         }
1263         final Intent fillInIntent;
1264         synchronized (mLock) {
1265             if (mDestroyed) {
1266                 Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
1267                         + id + " destroyed");
1268                 return;
1269             }
1270             fillInIntent = createAuthFillInIntentLocked(requestId, extras);
1271             if (fillInIntent == null) {
1272                 forceRemoveFromServiceLocked();
1273                 return;
1274             }
1275         }
1276 
1277         mService.setAuthenticationSelected(id, mClientState);
1278 
1279         final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
1280         mHandler.sendMessage(obtainMessage(
1281                 Session::startAuthentication,
1282                 this, authenticationId, intent, fillInIntent, authenticateInline));
1283     }
1284 
1285     // AutoFillUiCallback
1286     @Override
1287     public void fill(int requestId, int datasetIndex, Dataset dataset) {
1288         synchronized (mLock) {
1289             if (mDestroyed) {
1290                 Slog.w(TAG, "Call to Session#fill() rejected - session: "
1291                         + id + " destroyed");
1292                 return;
1293             }
1294         }
1295         mHandler.sendMessage(obtainMessage(
1296                 Session::autoFill,
1297                 this, requestId, datasetIndex, dataset, true));
1298     }
1299 
1300     // AutoFillUiCallback
1301     @Override
1302     public void save() {
1303         synchronized (mLock) {
1304             if (mDestroyed) {
1305                 Slog.w(TAG, "Call to Session#save() rejected - session: "
1306                         + id + " destroyed");
1307                 return;
1308             }
1309         }
1310         mHandler.sendMessage(obtainMessage(
1311                 AutofillManagerServiceImpl::handleSessionSave,
1312                 mService, this));
1313     }
1314 
1315     // AutoFillUiCallback
1316     @Override
1317     public void cancelSave() {
1318         synchronized (mLock) {
1319             mSessionFlags.mShowingSaveUi = false;
1320 
1321             if (mDestroyed) {
1322                 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
1323                         + id + " destroyed");
1324                 return;
1325             }
1326         }
1327         mHandler.sendMessage(obtainMessage(
1328                 Session::removeFromService, this));
1329     }
1330 
1331     // AutoFillUiCallback
1332     @Override
1333     public void requestShowFillUi(AutofillId id, int width, int height,
1334             IAutofillWindowPresenter presenter) {
1335         synchronized (mLock) {
1336             if (mDestroyed) {
1337                 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: "
1338                         + id + " destroyed");
1339                 return;
1340             }
1341             if (id.equals(mCurrentViewId)) {
1342                 try {
1343                     final ViewState view = mViewStates.get(id);
1344                     mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(),
1345                             presenter);
1346                 } catch (RemoteException e) {
1347                     Slog.e(TAG, "Error requesting to show fill UI", e);
1348                 }
1349             } else {
1350                 if (sDebug) {
1351                     Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view ("
1352                             + mCurrentViewId + ") anymore");
1353                 }
1354             }
1355         }
1356     }
1357 
1358     // AutoFillUiCallback
1359     @Override
1360     public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) {
1361         synchronized (mLock) {
1362             if (mDestroyed) {
1363                 Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: "
1364                         + id + " destroyed");
1365                 return;
1366             }
1367             if (id.equals(mCurrentViewId)) {
1368                 try {
1369                     mClient.dispatchUnhandledKey(this.id, id, keyEvent);
1370                 } catch (RemoteException e) {
1371                     Slog.e(TAG, "Error requesting to dispatch unhandled key", e);
1372                 }
1373             } else {
1374                 Slog.w(TAG, "Do not dispatch unhandled key on " + id
1375                         + " as it is not the current view (" + mCurrentViewId + ") anymore");
1376             }
1377         }
1378     }
1379 
1380     // AutoFillUiCallback
1381     @Override
1382     public void requestHideFillUi(AutofillId id) {
1383         synchronized (mLock) {
1384             // NOTE: We allow this call in a destroyed state as the UI is
1385             // asked to go away after we get destroyed, so let it do that.
1386             try {
1387                 mClient.requestHideFillUi(this.id, id);
1388             } catch (RemoteException e) {
1389                 Slog.e(TAG, "Error requesting to hide fill UI", e);
1390             }
1391 
1392             mInlineSessionController.hideInlineSuggestionsUiLocked(id);
1393         }
1394     }
1395 
1396     // AutoFillUiCallback
1397     @Override
1398     public void cancelSession() {
1399         synchronized (mLock) {
1400             removeFromServiceLocked();
1401         }
1402     }
1403 
1404     // AutoFillUiCallback
1405     @Override
1406     public void startIntentSenderAndFinishSession(IntentSender intentSender) {
1407         startIntentSender(intentSender, null);
1408     }
1409 
1410     // AutoFillUiCallback
1411     @Override
1412     public void startIntentSender(IntentSender intentSender, Intent intent) {
1413         synchronized (mLock) {
1414             if (mDestroyed) {
1415                 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: "
1416                         + id + " destroyed");
1417                 return;
1418             }
1419             if (intent == null) {
1420                 removeFromServiceLocked();
1421             }
1422         }
1423         mHandler.sendMessage(obtainMessage(
1424                 Session::doStartIntentSender,
1425                 this, intentSender, intent));
1426     }
1427 
1428     private void notifyFillUiHidden(@NonNull AutofillId autofillId) {
1429         synchronized (mLock) {
1430             try {
1431                 mClient.notifyFillUiHidden(this.id, autofillId);
1432             } catch (RemoteException e) {
1433                 Slog.e(TAG, "Error sending fill UI hidden notification", e);
1434             }
1435         }
1436     }
1437 
1438     private void notifyFillUiShown(@NonNull AutofillId autofillId) {
1439         synchronized (mLock) {
1440             try {
1441                 mClient.notifyFillUiShown(this.id, autofillId);
1442             } catch (RemoteException e) {
1443                 Slog.e(TAG, "Error sending fill UI shown notification", e);
1444             }
1445         }
1446     }
1447 
1448     private void doStartIntentSender(IntentSender intentSender, Intent intent) {
1449         try {
1450             synchronized (mLock) {
1451                 mClient.startIntentSender(intentSender, intent);
1452             }
1453         } catch (RemoteException e) {
1454             Slog.e(TAG, "Error launching auth intent", e);
1455         }
1456     }
1457 
1458     @GuardedBy("mLock")
1459     void setAuthenticationResultLocked(Bundle data, int authenticationId) {
1460         if (mDestroyed) {
1461             Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
1462                     + id + " destroyed");
1463             return;
1464         }
1465         final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
1466         if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
1467             setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId);
1468             return;
1469         }
1470         if (mResponses == null) {
1471             // Typically happens when app explicitly called cancel() while the service was showing
1472             // the auth UI.
1473             Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
1474             removeFromService();
1475             return;
1476         }
1477         final FillResponse authenticatedResponse = mResponses.get(requestId);
1478         if (authenticatedResponse == null || data == null) {
1479             Slog.w(TAG, "no authenticated response");
1480             removeFromService();
1481             return;
1482         }
1483 
1484         final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(
1485                 authenticationId);
1486         // Authenticated a dataset - reset view state regardless if we got a response or a dataset
1487         if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
1488             final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx);
1489             if (dataset == null) {
1490                 Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response");
1491                 removeFromService();
1492                 return;
1493             }
1494         }
1495 
1496         // The client becomes invisible for the authentication, the response is effective.
1497         mSessionFlags.mExpiredResponse = false;
1498 
1499         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
1500         final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
1501         if (sDebug) {
1502             Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
1503                     + ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
1504         }
1505         if (result instanceof FillResponse) {
1506             logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED);
1507             replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
1508         } else if (result instanceof Dataset) {
1509             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
1510                 logAuthenticationStatusLocked(requestId,
1511                         MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
1512                 if (newClientState != null) {
1513                     if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
1514                     mClientState = newClientState;
1515                 }
1516                 final Dataset dataset = (Dataset) result;
1517                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
1518                 if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
1519                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
1520                 }
1521                 autoFill(requestId, datasetIdx, dataset, false);
1522             } else {
1523                 Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id "
1524                         + authenticationId);
1525                 logAuthenticationStatusLocked(requestId,
1526                         MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
1527             }
1528         } else {
1529             if (result != null) {
1530                 Slog.w(TAG, "service returned invalid auth type: " + result);
1531             }
1532             logAuthenticationStatusLocked(requestId,
1533                     MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
1534             processNullResponseLocked(requestId, 0);
1535         }
1536     }
1537 
1538     /**
1539      * Returns whether the dataset returned from the authentication result is ephemeral or not.
1540      * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
1541      * information.
1542      */
1543     private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset,
1544             @NonNull Bundle authResultData) {
1545         if (authResultData.containsKey(
1546                 AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
1547             return authResultData.getBoolean(
1548                     AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET);
1549         }
1550         return isPinnedDataset(oldDataset);
1551     }
1552 
1553     /**
1554      * A dataset can potentially have multiple fields, and it's possible that some of the fields'
1555      * has inline presentation and some don't. It's also possible that some of the fields'
1556      * inline presentation is pinned and some isn't. So the concept of whether a dataset is
1557      * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a
1558      * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient
1559      * for most of the cases.
1560      */
1561     private static boolean isPinnedDataset(@Nullable Dataset dataset) {
1562         if (dataset != null && dataset.getFieldIds() != null) {
1563             final int numOfFields = dataset.getFieldIds().size();
1564             for (int i = 0; i < numOfFields; i++) {
1565                 final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(i);
1566                 if (inlinePresentation != null && inlinePresentation.isPinned()) {
1567                     return true;
1568                 }
1569             }
1570         }
1571         return false;
1572     }
1573 
1574     @GuardedBy("mLock")
1575     void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) {
1576         final Dataset dataset = (data == null) ? null :
1577                 data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
1578         if (sDebug) {
1579             Slog.d(TAG, "Auth result for augmented autofill: sessionId=" + id
1580                     + ", authId=" + authId + ", dataset=" + dataset);
1581         }
1582         final AutofillId fieldId = (dataset != null && dataset.getFieldIds().size() == 1)
1583                 ? dataset.getFieldIds().get(0) : null;
1584         final AutofillValue value = (dataset != null && dataset.getFieldValues().size() == 1)
1585                 ? dataset.getFieldValues().get(0) : null;
1586         final ClipData content = (dataset != null) ? dataset.getFieldContent() : null;
1587         if (fieldId == null || (value == null && content == null)) {
1588             if (sDebug) {
1589                 Slog.d(TAG, "Rejecting empty/invalid auth result");
1590             }
1591             mService.resetLastAugmentedAutofillResponse();
1592             removeFromServiceLocked();
1593             return;
1594         }
1595 
1596         // Get a handle to the RemoteAugmentedAutofillService. In
1597         // AutofillManagerServiceImpl.updateRemoteAugmentedAutofillService() we invalidate sessions
1598         // whenever the service changes, so there should never be a case when we get here and the
1599         // remote service instance is not present or different.
1600         final RemoteAugmentedAutofillService remoteAugmentedAutofillService =
1601                 mService.getRemoteAugmentedAutofillServiceIfCreatedLocked();
1602         if (remoteAugmentedAutofillService == null) {
1603             Slog.e(TAG, "Can't fill after auth: RemoteAugmentedAutofillService is null");
1604             mService.resetLastAugmentedAutofillResponse();
1605             removeFromServiceLocked();
1606             return;
1607         }
1608 
1609         // Update state to ensure that after filling the field here we don't end up firing another
1610         // autofill request that will end up showing the same suggestions to the user again. When
1611         // the auth activity came up, the field for which the suggestions were shown lost focus and
1612         // mCurrentViewId was cleared. We need to set mCurrentViewId back to the id of the field
1613         // that we are filling.
1614         fieldId.setSessionId(id);
1615         mCurrentViewId = fieldId;
1616 
1617         // Notify the Augmented Autofill provider of the dataset that was selected.
1618         final Bundle clientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
1619         mService.logAugmentedAutofillSelected(id, dataset.getId(), clientState);
1620 
1621         // For any content URIs, grant URI permissions to the target app before filling.
1622         if (content != null) {
1623             final AutofillUriGrantsManager autofillUgm =
1624                     remoteAugmentedAutofillService.getAutofillUriGrantsManager();
1625             autofillUgm.grantUriPermissions(mComponentName, mActivityToken, userId, content);
1626         }
1627 
1628         // Fill the value into the field.
1629         if (sDebug) {
1630             Slog.d(TAG, "Filling after auth: fieldId=" + fieldId + ", value=" + value
1631                     + ", content=" + content);
1632         }
1633         try {
1634             if (content != null) {
1635                 mClient.autofillContent(id, fieldId, content);
1636             } else {
1637                 mClient.autofill(id, dataset.getFieldIds(), dataset.getFieldValues(), true);
1638             }
1639         } catch (RemoteException e) {
1640             Slog.w(TAG, "Error filling after auth: fieldId=" + fieldId + ", value=" + value
1641                     + ", content=" + content, e);
1642         }
1643 
1644         // Clear the suggestions since the user already accepted one of them.
1645         mInlineSessionController.setInlineFillUiLocked(InlineFillUi.emptyUi(fieldId));
1646     }
1647 
1648     @GuardedBy("mLock")
1649     void setHasCallbackLocked(boolean hasIt) {
1650         if (mDestroyed) {
1651             Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
1652                     + id + " destroyed");
1653             return;
1654         }
1655         mHasCallback = hasIt;
1656     }
1657 
1658     @GuardedBy("mLock")
1659     @Nullable
1660     private FillResponse getLastResponseLocked(@Nullable String logPrefixFmt) {
1661         final String logPrefix = sDebug && logPrefixFmt != null
1662                 ? String.format(logPrefixFmt, this.id)
1663                 : null;
1664         if (mContexts == null) {
1665             if (logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
1666             return null;
1667         }
1668         if (mResponses == null) {
1669             // Happens when the activity / session was finished before the service replied, or
1670             // when the service cannot autofill it (and returned a null response).
1671             if (sVerbose && logPrefix != null) {
1672                 Slog.v(TAG, logPrefix + ": no responses on session");
1673             }
1674             return null;
1675         }
1676 
1677         final int lastResponseIdx = getLastResponseIndexLocked();
1678         if (lastResponseIdx < 0) {
1679             if (logPrefix != null) {
1680                 Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
1681                         + ", mViewStates=" + mViewStates);
1682             }
1683             return null;
1684         }
1685 
1686         final FillResponse response = mResponses.valueAt(lastResponseIdx);
1687         if (sVerbose && logPrefix != null) {
1688             Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
1689                     + ", mViewStates=" + mViewStates);
1690         }
1691         return response;
1692     }
1693 
1694     @GuardedBy("mLock")
1695     @Nullable
1696     private SaveInfo getSaveInfoLocked() {
1697         final FillResponse response = getLastResponseLocked(null);
1698         return response == null ? null : response.getSaveInfo();
1699     }
1700 
1701     @GuardedBy("mLock")
1702     int getSaveInfoFlagsLocked() {
1703         final SaveInfo saveInfo = getSaveInfoLocked();
1704         return saveInfo == null ? 0 : saveInfo.getFlags();
1705     }
1706 
1707     /**
1708      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
1709      * when necessary.
1710      */
1711     public void logContextCommitted() {
1712         mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
1713                 Event.NO_SAVE_UI_REASON_NONE));
1714     }
1715 
1716     /**
1717      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
1718      * when necessary.
1719      *
1720      * @param saveDialogNotShowReason The reason why a save dialog was not shown.
1721      */
1722     public void logContextCommitted(@NoSaveReason int saveDialogNotShowReason) {
1723         mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
1724                 saveDialogNotShowReason));
1725     }
1726 
1727     private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason) {
1728         final FillResponse lastResponse;
1729         synchronized (mLock) {
1730             lastResponse = getLastResponseLocked("logContextCommited(%s)");
1731         }
1732 
1733         if (lastResponse == null) {
1734             Slog.w(TAG, "handleLogContextCommitted(): last response is null");
1735             return;
1736         }
1737 
1738         // Merge UserData if necessary.
1739         // Fields in packageUserData will override corresponding fields in genericUserData.
1740         final UserData genericUserData = mService.getUserData();
1741         final UserData packageUserData = lastResponse.getUserData();
1742         final FieldClassificationUserData userData;
1743         if (packageUserData == null && genericUserData == null) {
1744             userData = null;
1745         } else if (packageUserData != null && genericUserData != null) {
1746             userData = new CompositeUserData(genericUserData, packageUserData);
1747         } else if (packageUserData != null) {
1748             userData = packageUserData;
1749         } else {
1750             userData = mService.getUserData();
1751         }
1752 
1753         final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
1754 
1755         // Sets field classification scores
1756         if (userData != null && fcStrategy != null) {
1757             logFieldClassificationScore(fcStrategy, userData, saveDialogNotShowReason);
1758         } else {
1759             logContextCommitted(null, null, saveDialogNotShowReason);
1760         }
1761     }
1762 
1763     private void logContextCommitted(@Nullable ArrayList<AutofillId> detectedFieldIds,
1764             @Nullable ArrayList<FieldClassification> detectedFieldClassifications,
1765             @NoSaveReason int saveDialogNotShowReason) {
1766         synchronized (mLock) {
1767             logContextCommittedLocked(detectedFieldIds, detectedFieldClassifications,
1768                     saveDialogNotShowReason);
1769         }
1770     }
1771 
1772     @GuardedBy("mLock")
1773     private void logContextCommittedLocked(@Nullable ArrayList<AutofillId> detectedFieldIds,
1774             @Nullable ArrayList<FieldClassification> detectedFieldClassifications,
1775             @NoSaveReason int saveDialogNotShowReason) {
1776         final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)");
1777         if (lastResponse == null) return;
1778 
1779         final int flags = lastResponse.getFlags();
1780         if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
1781             if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
1782             return;
1783         }
1784 
1785         ArraySet<String> ignoredDatasets = null;
1786         ArrayList<AutofillId> changedFieldIds = null;
1787         ArrayList<String> changedDatasetIds = null;
1788         ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
1789 
1790         boolean hasAtLeastOneDataset = false;
1791         final int responseCount = mResponses.size();
1792         for (int i = 0; i < responseCount; i++) {
1793             final FillResponse response = mResponses.valueAt(i);
1794             final List<Dataset> datasets = response.getDatasets();
1795             if (datasets == null || datasets.isEmpty()) {
1796                 if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
1797             } else {
1798                 for (int j = 0; j < datasets.size(); j++) {
1799                     final Dataset dataset = datasets.get(j);
1800                     final String datasetId = dataset.getId();
1801                     if (datasetId == null) {
1802                         if (sVerbose) {
1803                             Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
1804                         }
1805                     } else {
1806                         hasAtLeastOneDataset = true;
1807                         if (mSelectedDatasetIds == null
1808                                 || !mSelectedDatasetIds.contains(datasetId)) {
1809                             if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
1810                             if (ignoredDatasets == null) {
1811                                 ignoredDatasets = new ArraySet<>();
1812                             }
1813                             ignoredDatasets.add(datasetId);
1814                         }
1815                     }
1816                 }
1817             }
1818         }
1819         final AutofillId[] fieldClassificationIds = lastResponse.getFieldClassificationIds();
1820 
1821         if (!hasAtLeastOneDataset && fieldClassificationIds == null) {
1822             if (sVerbose) {
1823                 Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
1824                         + "classification ids)");
1825             }
1826             return;
1827         }
1828 
1829         for (int i = 0; i < mViewStates.size(); i++) {
1830             final ViewState viewState = mViewStates.valueAt(i);
1831             final int state = viewState.getState();
1832 
1833             // When value changed, we need to log if it was:
1834             // - autofilled -> changedDatasetIds
1835             // - not autofilled but matches a dataset value -> manuallyFilledIds
1836             if ((state & ViewState.STATE_CHANGED) != 0) {
1837                 // Check if autofilled value was changed
1838                 if ((state & ViewState.STATE_AUTOFILLED_ONCE) != 0) {
1839                     final String datasetId = viewState.getDatasetId();
1840                     if (datasetId == null) {
1841                         // Validation check - should never happen.
1842                         Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
1843                         continue;
1844                     }
1845 
1846                     // Must first check if final changed value is not the same as value sent by
1847                     // service.
1848                     final AutofillValue autofilledValue = viewState.getAutofilledValue();
1849                     final AutofillValue currentValue = viewState.getCurrentValue();
1850                     if (autofilledValue != null && autofilledValue.equals(currentValue)) {
1851                         if (sDebug) {
1852                             Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
1853                                     + " because it has same value that was autofilled");
1854                         }
1855                         continue;
1856                     }
1857 
1858                     if (sDebug) {
1859                         Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
1860                     }
1861                     if (changedFieldIds == null) {
1862                         changedFieldIds = new ArrayList<>();
1863                         changedDatasetIds = new ArrayList<>();
1864                     }
1865                     changedFieldIds.add(viewState.id);
1866                     changedDatasetIds.add(datasetId);
1867                 } else {
1868                     final AutofillValue currentValue = viewState.getCurrentValue();
1869                     if (currentValue == null) {
1870                         if (sDebug) {
1871                             Slog.d(TAG, "logContextCommitted(): skipping view without current "
1872                                     + "value ( " + viewState + ")");
1873                         }
1874                         continue;
1875                     }
1876                     // Check if value match a dataset.
1877                     if (hasAtLeastOneDataset) {
1878                         for (int j = 0; j < responseCount; j++) {
1879                             final FillResponse response = mResponses.valueAt(j);
1880                             final List<Dataset> datasets = response.getDatasets();
1881                             if (datasets == null || datasets.isEmpty()) {
1882                                 if (sVerbose) {
1883                                     Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
1884                                 }
1885                             } else {
1886                                 for (int k = 0; k < datasets.size(); k++) {
1887                                     final Dataset dataset = datasets.get(k);
1888                                     final String datasetId = dataset.getId();
1889                                     if (datasetId == null) {
1890                                         if (sVerbose) {
1891                                             Slog.v(TAG, "logContextCommitted() skipping idless "
1892                                                     + "dataset " + dataset);
1893                                         }
1894                                     } else {
1895                                         final ArrayList<AutofillValue> values =
1896                                                 dataset.getFieldValues();
1897                                         for (int l = 0; l < values.size(); l++) {
1898                                             final AutofillValue candidate = values.get(l);
1899                                             if (currentValue.equals(candidate)) {
1900                                                 if (sDebug) {
1901                                                     Slog.d(TAG, "field " + viewState.id + " was "
1902                                                             + "manually filled with value set by "
1903                                                             + "dataset " + datasetId);
1904                                                 }
1905                                                 if (manuallyFilledIds == null) {
1906                                                     manuallyFilledIds = new ArrayMap<>();
1907                                                 }
1908                                                 ArraySet<String> datasetIds =
1909                                                         manuallyFilledIds.get(viewState.id);
1910                                                 if (datasetIds == null) {
1911                                                     datasetIds = new ArraySet<>(1);
1912                                                     manuallyFilledIds.put(viewState.id, datasetIds);
1913                                                 }
1914                                                 datasetIds.add(datasetId);
1915                                             }
1916                                         } // for l
1917                                         if (mSelectedDatasetIds == null
1918                                                 || !mSelectedDatasetIds.contains(datasetId)) {
1919                                             if (sVerbose) {
1920                                                 Slog.v(TAG, "adding ignored dataset " + datasetId);
1921                                             }
1922                                             if (ignoredDatasets == null) {
1923                                                 ignoredDatasets = new ArraySet<>();
1924                                             }
1925                                             ignoredDatasets.add(datasetId);
1926                                         } // if
1927                                     } // if
1928                                 } // for k
1929                             } // else
1930                         } // for j
1931                     }
1932 
1933                 } // else
1934             } // else
1935         }
1936 
1937         ArrayList<AutofillId> manuallyFilledFieldIds = null;
1938         ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
1939 
1940         // Must "flatten" the map to the parcelable collection primitives
1941         if (manuallyFilledIds != null) {
1942             final int size = manuallyFilledIds.size();
1943             manuallyFilledFieldIds = new ArrayList<>(size);
1944             manuallyFilledDatasetIds = new ArrayList<>(size);
1945             for (int i = 0; i < size; i++) {
1946                 final AutofillId fieldId = manuallyFilledIds.keyAt(i);
1947                 final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
1948                 manuallyFilledFieldIds.add(fieldId);
1949                 manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
1950             }
1951         }
1952 
1953         mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
1954                 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
1955                 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
1956                 mComponentName, mCompatMode, saveDialogNotShowReason);
1957     }
1958 
1959     /**
1960      * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
1961      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
1962      */
1963     private void logFieldClassificationScore(@NonNull FieldClassificationStrategy fcStrategy,
1964             @NonNull FieldClassificationUserData userData,
1965             @NoSaveReason int saveDialogNotShowReason) {
1966 
1967         final String[] userValues = userData.getValues();
1968         final String[] categoryIds = userData.getCategoryIds();
1969 
1970         final String defaultAlgorithm = userData.getFieldClassificationAlgorithm();
1971         final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
1972 
1973         final ArrayMap<String, String> algorithms = userData.getFieldClassificationAlgorithms();
1974         final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
1975 
1976         // Validation check
1977         if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) {
1978             final int valuesLength = userValues == null ? -1 : userValues.length;
1979             final int idsLength = categoryIds == null ? -1 : categoryIds.length;
1980             Slog.w(TAG, "setScores(): user data mismatch: values.length = "
1981                     + valuesLength + ", ids.length = " + idsLength);
1982             return;
1983         }
1984 
1985         final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
1986 
1987         final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize);
1988         final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
1989                 maxFieldsSize);
1990 
1991         final Collection<ViewState> viewStates;
1992         synchronized (mLock) {
1993             viewStates = mViewStates.values();
1994         }
1995 
1996         final int viewsSize = viewStates.size();
1997 
1998         // First, we get all scores.
1999         final AutofillId[] autofillIds = new AutofillId[viewsSize];
2000         final ArrayList<AutofillValue> currentValues = new ArrayList<>(viewsSize);
2001         int k = 0;
2002         for (ViewState viewState : viewStates) {
2003             currentValues.add(viewState.getCurrentValue());
2004             autofillIds[k++] = viewState.id;
2005         }
2006 
2007         // Then use the results, asynchronously
2008         final RemoteCallback callback = new RemoteCallback((result) -> {
2009             if (result == null) {
2010                 if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
2011                 logContextCommitted(null, null, saveDialogNotShowReason);
2012                 return;
2013             }
2014             final Scores scores = result.getParcelable(EXTRA_SCORES);
2015             if (scores == null) {
2016                 Slog.w(TAG, "No field classification score on " + result);
2017                 return;
2018             }
2019             int i = 0, j = 0;
2020             try {
2021                 // Iteract over all autofill fields first
2022                 for (i = 0; i < viewsSize; i++) {
2023                     final AutofillId autofillId = autofillIds[i];
2024 
2025                     // Search the best scores for each category (as some categories could have
2026                     // multiple user values
2027                     ArrayMap<String, Float> scoresByField = null;
2028                     for (j = 0; j < userValues.length; j++) {
2029                         final String categoryId = categoryIds[j];
2030                         final float score = scores.scores[i][j];
2031                         if (score > 0) {
2032                             if (scoresByField == null) {
2033                                 scoresByField = new ArrayMap<>(userValues.length);
2034                             }
2035                             final Float currentScore = scoresByField.get(categoryId);
2036                             if (currentScore != null && currentScore > score) {
2037                                 if (sVerbose) {
2038                                     Slog.v(TAG, "skipping score " + score
2039                                             + " because it's less than " + currentScore);
2040                                 }
2041                                 continue;
2042                             }
2043                             if (sVerbose) {
2044                                 Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
2045                                         + autofillId);
2046                             }
2047                             scoresByField.put(categoryId, score);
2048                         } else if (sVerbose) {
2049                             Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
2050                         }
2051                     }
2052                     if (scoresByField == null) {
2053                         if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
2054                         continue;
2055                     }
2056 
2057                     // Then create the matches for that autofill id
2058                     final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
2059                     for (j = 0; j < scoresByField.size(); j++) {
2060                         final String fieldId = scoresByField.keyAt(j);
2061                         final float score = scoresByField.valueAt(j);
2062                         matches.add(new Match(fieldId, score));
2063                     }
2064                     detectedFieldIds.add(autofillId);
2065                     detectedFieldClassifications.add(new FieldClassification(matches));
2066                 } // for i
2067             } catch (ArrayIndexOutOfBoundsException e) {
2068                 wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
2069                 return;
2070             }
2071 
2072             logContextCommitted(detectedFieldIds, detectedFieldClassifications,
2073                     saveDialogNotShowReason);
2074         });
2075 
2076         fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
2077                 defaultAlgorithm, defaultArgs, algorithms, args);
2078     }
2079 
2080     /**
2081      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN}
2082      * when necessary.
2083      *
2084      * <p>Note: It is necessary to call logContextCommitted() first before calling this method.
2085      */
2086     public void logSaveUiShown() {
2087         mHandler.sendMessage(obtainMessage(Session::logSaveShown, this));
2088     }
2089 
2090     /**
2091      * Shows the save UI, when session can be saved.
2092      *
2093      * @return {@link SaveResult} that contains the save ui display status information.
2094      */
2095     @GuardedBy("mLock")
2096     @NonNull
2097     public SaveResult showSaveLocked() {
2098         if (mDestroyed) {
2099             Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
2100                     + id + " destroyed");
2101             return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false,
2102                     Event.NO_SAVE_UI_REASON_NONE);
2103         }
2104         mSessionState = STATE_FINISHED;
2105         final FillResponse response = getLastResponseLocked("showSaveLocked(%s)");
2106         final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
2107 
2108         /*
2109          * The Save dialog is only shown if all conditions below are met:
2110          *
2111          * - saveInfo is not null.
2112          * - autofillValue of all required ids is not null.
2113          * - autofillValue of at least one id (required or optional) has changed.
2114          * - there is no Dataset in the last FillResponse whose values of all dataset fields matches
2115          *   the current values of all fields in the screen.
2116          * - server didn't ask to keep session alive
2117          */
2118         if (saveInfo == null) {
2119             if (sVerbose) Slog.v(TAG, "showSaveLocked(" + this.id + "): no saveInfo from service");
2120             return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
2121                     Event.NO_SAVE_UI_REASON_NO_SAVE_INFO);
2122         }
2123 
2124         if ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0) {
2125             // TODO(b/113281366): log metrics
2126             if (sDebug) Slog.v(TAG, "showSaveLocked(" + this.id + "): service asked to delay save");
2127             return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false,
2128                     Event.NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG);
2129         }
2130 
2131         final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
2132 
2133         // Cache used to make sure changed fields do not belong to a dataset.
2134         final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
2135         // Savable (optional or required) ids that will be checked against the dataset ids.
2136         final ArraySet<AutofillId> savableIds = new ArraySet<>();
2137 
2138         final AutofillId[] requiredIds = saveInfo.getRequiredIds();
2139         boolean allRequiredAreNotEmpty = true;
2140         boolean atLeastOneChanged = false;
2141         // If an autofilled field is changed, we need to change isUpdate to true so the proper UI is
2142         // shown.
2143         boolean isUpdate = false;
2144         if (requiredIds != null) {
2145             for (int i = 0; i < requiredIds.length; i++) {
2146                 final AutofillId id = requiredIds[i];
2147                 if (id == null) {
2148                     Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds));
2149                     continue;
2150                 }
2151                 savableIds.add(id);
2152                 final ViewState viewState = mViewStates.get(id);
2153                 if (viewState == null) {
2154                     Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
2155                     allRequiredAreNotEmpty = false;
2156                     break;
2157                 }
2158 
2159                 AutofillValue value = viewState.getCurrentValue();
2160                 if (value == null || value.isEmpty()) {
2161                     final AutofillValue initialValue = getValueFromContextsLocked(id);
2162                     if (initialValue != null) {
2163                         if (sDebug) {
2164                             Slog.d(TAG, "Value of required field " + id + " didn't change; "
2165                                     + "using initial value (" + initialValue + ") instead");
2166                         }
2167                         value = initialValue;
2168                     } else {
2169                         if (sDebug) {
2170                             Slog.d(TAG, "empty value for required " + id );
2171                         }
2172                         allRequiredAreNotEmpty = false;
2173                         break;
2174                     }
2175                 }
2176 
2177                 value = getSanitizedValue(sanitizers, id, value);
2178                 if (value == null) {
2179                     if (sDebug) {
2180                         Slog.d(TAG, "value of required field " + id + " failed sanitization");
2181                     }
2182                     allRequiredAreNotEmpty = false;
2183                     break;
2184                 }
2185                 viewState.setSanitizedValue(value);
2186                 currentValues.put(id, value);
2187                 final AutofillValue filledValue = viewState.getAutofilledValue();
2188 
2189                 if (!value.equals(filledValue)) {
2190                     boolean changed = true;
2191                     if (filledValue == null) {
2192                         // Dataset was not autofilled, make sure initial value didn't change.
2193                         final AutofillValue initialValue = getValueFromContextsLocked(id);
2194                         if (initialValue != null && initialValue.equals(value)) {
2195                             if (sDebug) {
2196                                 Slog.d(TAG, "id " + id + " is part of dataset but initial value "
2197                                         + "didn't change: " + value);
2198                             }
2199                             changed = false;
2200                         }
2201                     } else {
2202                         isUpdate = true;
2203                     }
2204                     if (changed) {
2205                         if (sDebug) {
2206                             Slog.d(TAG, "found a change on required " + id + ": " + filledValue
2207                                     + " => " + value);
2208                         }
2209                         atLeastOneChanged = true;
2210                     }
2211                 }
2212             }
2213         }
2214 
2215         final AutofillId[] optionalIds = saveInfo.getOptionalIds();
2216         if (sVerbose) {
2217             Slog.v(TAG, "allRequiredAreNotEmpty: " + allRequiredAreNotEmpty + " hasOptional: "
2218                     + (optionalIds != null));
2219         }
2220         int saveDialogNotShowReason;
2221         if (!allRequiredAreNotEmpty) {
2222             saveDialogNotShowReason = Event.NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED;
2223         } else {
2224             // Must look up all optional ids in 2 scenarios:
2225             // - if no required id changed but an optional id did, it should trigger save / update
2226             // - if at least one required id changed but it was not part of a filled dataset, we
2227             //   need to check if an optional id is part of a filled datased (in which case we show
2228             //   Update instead of Save)
2229             if (optionalIds!= null && (!atLeastOneChanged || !isUpdate)) {
2230                 // No change on required ids yet, look for changes on optional ids.
2231                 for (int i = 0; i < optionalIds.length; i++) {
2232                     final AutofillId id = optionalIds[i];
2233                     savableIds.add(id);
2234                     final ViewState viewState = mViewStates.get(id);
2235                     if (viewState == null) {
2236                         Slog.w(TAG, "no ViewState for optional " + id);
2237                         continue;
2238                     }
2239                     if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
2240                         final AutofillValue currentValue = viewState.getCurrentValue();
2241                         final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue);
2242                         if (value == null) {
2243                             if (sDebug) {
2244                                 Slog.d(TAG, "value of opt. field " + id + " failed sanitization");
2245                             }
2246                             continue;
2247                         }
2248 
2249                         currentValues.put(id, value);
2250                         final AutofillValue filledValue = viewState.getAutofilledValue();
2251                         if (value != null && !value.equals(filledValue)) {
2252                             if (sDebug) {
2253                                 Slog.d(TAG, "found a change on optional " + id + ": " + filledValue
2254                                         + " => " + value);
2255                             }
2256                             if (filledValue != null) {
2257                                 isUpdate = true;
2258                             }
2259                             atLeastOneChanged = true;
2260                         }
2261                     } else  {
2262                         // Update current values cache based on initial value
2263                         final AutofillValue initialValue = getValueFromContextsLocked(id);
2264                         if (sDebug) {
2265                             Slog.d(TAG, "no current value for " + id + "; initial value is "
2266                                     + initialValue);
2267                         }
2268                         if (initialValue != null) {
2269                             currentValues.put(id, initialValue);
2270                         }
2271                     }
2272                 }
2273             }
2274             if (!atLeastOneChanged) {
2275                 saveDialogNotShowReason = Event.NO_SAVE_UI_REASON_NO_VALUE_CHANGED;
2276             } else {
2277                 if (sDebug) {
2278                     Slog.d(TAG, "at least one field changed, validate fields for save UI");
2279                 }
2280                 final InternalValidator validator = saveInfo.getValidator();
2281                 if (validator != null) {
2282                     final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
2283                     boolean isValid;
2284                     try {
2285                         isValid = validator.isValid(this);
2286                         if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
2287                         log.setType(isValid
2288                                 ? MetricsEvent.TYPE_SUCCESS
2289                                 : MetricsEvent.TYPE_DISMISS);
2290                     } catch (Exception e) {
2291                         Slog.e(TAG, "Not showing save UI because validation failed:", e);
2292                         log.setType(MetricsEvent.TYPE_FAILURE);
2293                         mMetricsLogger.write(log);
2294                         return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
2295                                 Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED);
2296                     }
2297 
2298                     mMetricsLogger.write(log);
2299                     if (!isValid) {
2300                         Slog.i(TAG, "not showing save UI because fields failed validation");
2301                         return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
2302                                 Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED);
2303                     }
2304                 }
2305 
2306                 // Make sure the service doesn't have the fields already by checking the datasets
2307                 // content.
2308                 final List<Dataset> datasets = response.getDatasets();
2309                 if (datasets != null) {
2310                     datasets_loop: for (int i = 0; i < datasets.size(); i++) {
2311                         final Dataset dataset = datasets.get(i);
2312                         final ArrayMap<AutofillId, AutofillValue> datasetValues =
2313                                 Helper.getFields(dataset);
2314                         if (sVerbose) {
2315                             Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i
2316                                     + ": " + dataset + "; savableIds=" + savableIds);
2317                         }
2318                         savable_ids_loop: for (int j = 0; j < savableIds.size(); j++) {
2319                             final AutofillId id = savableIds.valueAt(j);
2320                             final AutofillValue currentValue = currentValues.get(id);
2321                             if (currentValue == null) {
2322                                 if (sDebug) {
2323                                     Slog.d(TAG, "dataset has value for field that is null: " + id);
2324                                 }
2325                                 continue savable_ids_loop;
2326                             }
2327                             final AutofillValue datasetValue = datasetValues.get(id);
2328                             if (!currentValue.equals(datasetValue)) {
2329                                 if (sDebug) {
2330                                     Slog.d(TAG, "found a dataset change on id " + id + ": from "
2331                                             + datasetValue + " to " + currentValue);
2332                                 }
2333                                 continue datasets_loop;
2334                             }
2335                             if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id);
2336                         }
2337                         if (sDebug) {
2338                             Slog.d(TAG, "ignoring Save UI because all fields match contents of "
2339                                     + "dataset #" + i + ": " + dataset);
2340                         }
2341                         return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
2342                                 Event.NO_SAVE_UI_REASON_DATASET_MATCH);
2343                     }
2344                 }
2345 
2346                 if (sDebug) {
2347                     Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
2348                             + id + "!");
2349                 }
2350 
2351                 final IAutoFillManagerClient client = getClient();
2352                 mPendingSaveUi = new PendingUi(new Binder(), id, client);
2353 
2354                 final CharSequence serviceLabel;
2355                 final Drawable serviceIcon;
2356                 synchronized (mLock) {
2357                     serviceLabel = mService.getServiceLabelLocked();
2358                     serviceIcon = mService.getServiceIconLocked();
2359                 }
2360                 if (serviceLabel == null || serviceIcon == null) {
2361                     wtf(null, "showSaveLocked(): no service label or icon");
2362                     return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
2363                             Event.NO_SAVE_UI_REASON_NONE);
2364                 }
2365 
2366                 getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
2367                         mService.getServicePackageName(), saveInfo, this,
2368                         mComponentName, this, mPendingSaveUi, isUpdate, mCompatMode);
2369                 if (client != null) {
2370                     try {
2371                         client.setSaveUiState(id, true);
2372                     } catch (RemoteException e) {
2373                         Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e);
2374                     }
2375                 }
2376                 mSessionFlags.mShowingSaveUi = true;
2377                 return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false,
2378                         Event.NO_SAVE_UI_REASON_NONE);
2379             }
2380         }
2381         // Nothing changed...
2382         if (sDebug) {
2383             Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities."
2384                     + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
2385                     + ", atLeastOneChanged=" + atLeastOneChanged);
2386         }
2387         return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
2388                 saveDialogNotShowReason);
2389     }
2390 
2391     private void logSaveShown() {
2392         mService.logSaveShown(id, mClientState);
2393     }
2394 
2395     @Nullable
2396     private AutofillValue getSanitizedValue(
2397             @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
2398             @NonNull AutofillId id,
2399             @Nullable AutofillValue value) {
2400         if (sanitizers == null || value == null) return value;
2401 
2402         final ViewState state = mViewStates.get(id);
2403         AutofillValue sanitized = state == null ? null : state.getSanitizedValue();
2404         if (sanitized == null) {
2405             final InternalSanitizer sanitizer = sanitizers.get(id);
2406             if (sanitizer == null) {
2407                 return value;
2408             }
2409 
2410             sanitized = sanitizer.sanitize(value);
2411             if (sDebug) {
2412                 Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
2413             }
2414             if (state != null) {
2415                 state.setSanitizedValue(sanitized);
2416             }
2417         }
2418         return sanitized;
2419     }
2420 
2421     /**
2422      * Returns whether the session is currently showing the save UI
2423      */
2424     @GuardedBy("mLock")
2425     boolean isSaveUiShowingLocked() {
2426         return mSessionFlags.mShowingSaveUi;
2427     }
2428 
2429     /**
2430      * Gets the latest non-empty value for the given id in the autofill contexts.
2431      */
2432     @GuardedBy("mLock")
2433     @Nullable
2434     private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
2435         final int numContexts = mContexts.size();
2436         for (int i = numContexts - 1; i >= 0; i--) {
2437             final FillContext context = mContexts.get(i);
2438             final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
2439                     autofillId);
2440             if (node != null) {
2441                 final AutofillValue value = node.getAutofillValue();
2442                 if (sDebug) {
2443                     Slog.d(TAG, "getValueFromContexts(" + this.id + "/" + autofillId + ") at "
2444                             + i + ": " + value);
2445                 }
2446                 if (value != null && !value.isEmpty()) {
2447                     return value;
2448                 }
2449             }
2450         }
2451         return null;
2452     }
2453 
2454     /**
2455      * Gets the latest autofill options for the given id in the autofill contexts.
2456      */
2457     @GuardedBy("mLock")
2458     @Nullable
2459     private CharSequence[] getAutofillOptionsFromContextsLocked(@NonNull AutofillId autofillId) {
2460         final int numContexts = mContexts.size();
2461         for (int i = numContexts - 1; i >= 0; i--) {
2462             final FillContext context = mContexts.get(i);
2463             final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
2464                     autofillId);
2465             if (node != null && node.getAutofillOptions() != null) {
2466                 return node.getAutofillOptions();
2467             }
2468         }
2469         return null;
2470     }
2471 
2472     /**
2473      * Update the {@link AutofillValue values} of the {@link AssistStructure} before sending it to
2474      * the service on save().
2475      */
2476     private void updateValuesForSaveLocked() {
2477         final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
2478                 createSanitizers(getSaveInfoLocked());
2479 
2480         final int numContexts = mContexts.size();
2481         for (int contextNum = 0; contextNum < numContexts; contextNum++) {
2482             final FillContext context = mContexts.get(contextNum);
2483 
2484             final ViewNode[] nodes =
2485                 context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
2486 
2487             if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): updating " + context);
2488 
2489             for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
2490                 final ViewState viewState = mViewStates.valueAt(viewStateNum);
2491 
2492                 final AutofillId id = viewState.id;
2493                 final AutofillValue value = viewState.getCurrentValue();
2494                 if (value == null) {
2495                     if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): skipping " + id);
2496                     continue;
2497                 }
2498                 final ViewNode node = nodes[viewStateNum];
2499                 if (node == null) {
2500                     Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
2501                     continue;
2502                 }
2503                 if (sVerbose) {
2504                     Slog.v(TAG, "updateValuesForSaveLocked(): updating " + id + " to " + value);
2505                 }
2506 
2507                 AutofillValue sanitizedValue = viewState.getSanitizedValue();
2508 
2509                 if (sanitizedValue == null) {
2510                     // Field is optional and haven't been sanitized yet.
2511                     sanitizedValue = getSanitizedValue(sanitizers, id, value);
2512                 }
2513                 if (sanitizedValue != null) {
2514                     node.updateAutofillValue(sanitizedValue);
2515                 } else if (sDebug) {
2516                     Slog.d(TAG, "updateValuesForSaveLocked(): not updating field " + id
2517                             + " because it failed sanitization");
2518                 }
2519             }
2520 
2521             // Sanitize structure before it's sent to service.
2522             context.getStructure().sanitizeForParceling(false);
2523 
2524             if (sVerbose) {
2525                 Slog.v(TAG, "updateValuesForSaveLocked(): dumping structure of " + context
2526                         + " before calling service.save()");
2527                 context.getStructure().dump(false);
2528             }
2529         }
2530     }
2531 
2532     /**
2533      * Calls service when user requested save.
2534      */
2535     @GuardedBy("mLock")
2536     void callSaveLocked() {
2537         if (mDestroyed) {
2538             Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
2539                     + id + " destroyed");
2540             return;
2541         }
2542         if (mRemoteFillService == null) {
2543             wtf(null, "callSaveLocked() called without a remote service. "
2544                     + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
2545             return;
2546         }
2547 
2548         if (sVerbose) Slog.v(TAG, "callSaveLocked(" + this.id + "): mViewStates=" + mViewStates);
2549 
2550         if (mContexts == null) {
2551             Slog.w(TAG, "callSaveLocked(): no contexts");
2552             return;
2553         }
2554 
2555         updateValuesForSaveLocked();
2556 
2557         // Remove pending fill requests as the session is finished.
2558         cancelCurrentRequestLocked();
2559 
2560         final ArrayList<FillContext> contexts = mergePreviousSessionLocked( /* forSave= */ true);
2561 
2562         final SaveRequest saveRequest =
2563                 new SaveRequest(contexts, mClientState, mSelectedDatasetIds);
2564         mRemoteFillService.onSaveRequest(saveRequest);
2565     }
2566 
2567     // TODO(b/113281366): rather than merge it here, it might be better to simply reuse the old
2568     // session instead of creating a new one. But we need to consider what would happen on corner
2569     // cases such as "Main Activity M -> activity A with username -> activity B with password"
2570     // If user follows the normal workflow, then session A would be merged with session B as
2571     // expected. But if when on Activity A the user taps back or somehow launches another activity,
2572     // session A could be merged with the wrong session.
2573     /**
2574      * Gets a list of contexts that includes not only this session's contexts but also the contexts
2575      * from previous sessions that were asked by the service to be delayed (if any).
2576      *
2577      * <p>As a side-effect:
2578      * <ul>
2579      *   <li>If the current {@link #mClientState} is {@code null}, sets it with the last non-
2580      *   {@code null} client state from previous sessions.
2581      *   <li>When {@code forSave} is {@code true}, calls {@link #updateValuesForSaveLocked()} in the
2582      *   previous sessions.
2583      * </ul>
2584      */
2585     @NonNull
2586     private ArrayList<FillContext> mergePreviousSessionLocked(boolean forSave) {
2587         final ArrayList<Session> previousSessions = mService.getPreviousSessionsLocked(this);
2588         final ArrayList<FillContext> contexts;
2589         if (previousSessions != null) {
2590             if (sDebug) {
2591                 Slog.d(TAG, "mergeSessions(" + this.id + "): Merging the content of "
2592                         + previousSessions.size() + " sessions for task " + taskId);
2593             }
2594             contexts = new ArrayList<>();
2595             for (int i = 0; i < previousSessions.size(); i++) {
2596                 final Session previousSession = previousSessions.get(i);
2597                 final ArrayList<FillContext> previousContexts = previousSession.mContexts;
2598                 if (previousContexts == null) {
2599                     Slog.w(TAG, "mergeSessions(" + this.id + "): Not merging null contexts from "
2600                             + previousSession.id);
2601                     continue;
2602                 }
2603                 if (forSave) {
2604                     previousSession.updateValuesForSaveLocked();
2605                 }
2606                 if (sDebug) {
2607                     Slog.d(TAG, "mergeSessions(" + this.id + "): adding " + previousContexts.size()
2608                             + " context from previous session #" + previousSession.id);
2609                 }
2610                 contexts.addAll(previousContexts);
2611                 if (mClientState == null && previousSession.mClientState != null) {
2612                     if (sDebug) {
2613                         Slog.d(TAG, "mergeSessions(" + this.id + "): setting client state from "
2614                                 + "previous session" + previousSession.id);
2615                     }
2616                     mClientState = previousSession.mClientState;
2617                 }
2618             }
2619             contexts.addAll(mContexts);
2620         } else {
2621             // Dispatch a snapshot of the current contexts list since it may change
2622             // until the dispatch happens. The items in the list don't need to be cloned
2623             // since we don't hold on them anywhere else. The client state is not touched
2624             // by us, so no need to copy.
2625             contexts = new ArrayList<>(mContexts);
2626         }
2627         return contexts;
2628     }
2629 
2630     /**
2631      * Starts (if necessary) a new fill request upon entering a view.
2632      *
2633      * <p>A new request will be started in 2 scenarios:
2634      * <ol>
2635      *   <li>If the user manually requested autofill.
2636      *   <li>If the view is part of a new partition.
2637      * </ol>
2638      *
2639      * @param id The id of the view that is entered.
2640      * @param viewState The view that is entered.
2641      * @param flags The flag that was passed by the AutofillManager.
2642      *
2643      * @return {@code true} if a new fill response is requested.
2644      */
2645     @GuardedBy("mLock")
2646     private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
2647             @NonNull ViewState viewState, int flags) {
2648         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
2649             mSessionFlags.mAugmentedAutofillOnly = false;
2650             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
2651             requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
2652             return true;
2653         }
2654 
2655         // If it's not, then check if it it should start a partition.
2656         if (shouldStartNewPartitionLocked(id)) {
2657             if (sDebug) {
2658                 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
2659                         + viewState.getStateAsString());
2660             }
2661             // Fix to always let standard autofill start.
2662             // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as
2663             // augmentedOnly, but other fields are still fillable by standard autofill.
2664             mSessionFlags.mAugmentedAutofillOnly = false;
2665             requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
2666             return true;
2667         }
2668 
2669         if (sVerbose) {
2670             Slog.v(TAG, "Not starting new partition for view " + id + ": "
2671                     + viewState.getStateAsString());
2672         }
2673         return false;
2674     }
2675 
2676     /**
2677      * Determines if a new partition should be started for an id.
2678      *
2679      * @param id The id of the view that is entered
2680      *
2681      * @return {@code true} if a new partition should be started
2682      */
2683     @GuardedBy("mLock")
2684     private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
2685         final ViewState currentView = mViewStates.get(id);
2686         if (mResponses == null) {
2687             return currentView != null && (currentView.getState()
2688                     & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0;
2689         }
2690 
2691         if (mSessionFlags.mExpiredResponse) {
2692             if (sDebug) {
2693                 Slog.d(TAG, "Starting a new partition because the response has expired.");
2694             }
2695             return true;
2696         }
2697 
2698         final int numResponses = mResponses.size();
2699         if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
2700             Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
2701                     + " reached maximum of " + AutofillManagerService.getPartitionMaxCount());
2702             return false;
2703         }
2704 
2705         for (int responseNum = 0; responseNum < numResponses; responseNum++) {
2706             final FillResponse response = mResponses.valueAt(responseNum);
2707 
2708             if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
2709                 return false;
2710             }
2711 
2712             final SaveInfo saveInfo = response.getSaveInfo();
2713             if (saveInfo != null) {
2714                 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id)
2715                         || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) {
2716                     return false;
2717                 }
2718             }
2719 
2720             final List<Dataset> datasets = response.getDatasets();
2721             if (datasets != null) {
2722                 final int numDatasets = datasets.size();
2723 
2724                 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) {
2725                     final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds();
2726 
2727                     if (fields != null && fields.contains(id)) {
2728                         return false;
2729                     }
2730                 }
2731             }
2732 
2733             if (ArrayUtils.contains(response.getAuthenticationIds(), id)) {
2734                 return false;
2735             }
2736         }
2737 
2738         return true;
2739     }
2740 
2741     @GuardedBy("mLock")
2742     void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
2743             int flags) {
2744         if (mDestroyed) {
2745             Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
2746                     + id + " destroyed");
2747             return;
2748         }
2749         if (action == ACTION_RESPONSE_EXPIRED) {
2750             mSessionFlags.mExpiredResponse = true;
2751             if (sDebug) {
2752                 Slog.d(TAG, "Set the response has expired.");
2753             }
2754             return;
2755         }
2756 
2757         id.setSessionId(this.id);
2758         if (sVerbose) {
2759             Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
2760                     + actionAsString(action) + ", flags=" + flags);
2761         }
2762         ViewState viewState = mViewStates.get(id);
2763         if (sVerbose) {
2764             Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId
2765                     + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
2766                     + ", viewState=" + viewState);
2767         }
2768 
2769         if (viewState == null) {
2770             if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED
2771                     || action == ACTION_VIEW_ENTERED) {
2772                 if (sVerbose) Slog.v(TAG, "Creating viewState for " + id);
2773                 boolean isIgnored = isIgnoredLocked(id);
2774                 viewState = new ViewState(id, this,
2775                         isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
2776                 mViewStates.put(id, viewState);
2777 
2778                 // TODO(b/73648631): for optimization purposes, should also ignore if change is
2779                 // detectable, and batch-send them when the session is finished (but that will
2780                 // require tracking detectable fields on AutofillManager)
2781                 if (isIgnored) {
2782                     if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + viewState);
2783                     return;
2784                 }
2785             } else {
2786                 if (sVerbose) Slog.v(TAG, "Ignoring specific action when viewState=null");
2787                 return;
2788             }
2789         }
2790 
2791         switch(action) {
2792             case ACTION_START_SESSION:
2793                 // View is triggering autofill.
2794                 mCurrentViewId = viewState.id;
2795                 viewState.update(value, virtualBounds, flags);
2796                 requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
2797                 break;
2798             case ACTION_VALUE_CHANGED:
2799                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
2800                     // Must cancel the session if the value of the URL bar changed
2801                     final String currentUrl = mUrlBar == null ? null
2802                             : mUrlBar.getText().toString().trim();
2803                     if (currentUrl == null) {
2804                         // Validation check - shouldn't happen.
2805                         wtf(null, "URL bar value changed, but current value is null");
2806                         return;
2807                     }
2808                     if (value == null || ! value.isText()) {
2809                         // Validation check - shouldn't happen.
2810                         wtf(null, "URL bar value changed to null or non-text: %s", value);
2811                         return;
2812                     }
2813                     final String newUrl = value.getTextValue().toString();
2814                     if (newUrl.equals(currentUrl)) {
2815                         if (sDebug) Slog.d(TAG, "Ignoring change on URL bar as it's the same");
2816                         return;
2817                     }
2818                     if (mSaveOnAllViewsInvisible) {
2819                         // We cannot cancel the session because it could hinder Save when all views
2820                         // are finished, as the URL bar changed callback is usually called before
2821                         // the virtual views become invisible.
2822                         if (sDebug) {
2823                             Slog.d(TAG, "Ignoring change on URL because session will finish when "
2824                                     + "views are gone");
2825                         }
2826                         return;
2827                     }
2828                     if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed");
2829                     forceRemoveFromServiceLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE);
2830                     return;
2831                 }
2832                 if (!Objects.equals(value, viewState.getCurrentValue())) {
2833                     logIfViewClearedLocked(id, value, viewState);
2834                     updateViewStateAndUiOnValueChangedLocked(id, value, viewState, flags);
2835                 }
2836                 break;
2837             case ACTION_VIEW_ENTERED:
2838                 if (sVerbose && virtualBounds != null) {
2839                     Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
2840                 }
2841 
2842                 final boolean isSameViewEntered = Objects.equals(mCurrentViewId, viewState.id);
2843                 // Update the view states first...
2844                 mCurrentViewId = viewState.id;
2845                 if (value != null) {
2846                     viewState.setCurrentValue(value);
2847                 }
2848 
2849                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
2850                     if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
2851                     return;
2852                 }
2853 
2854                 if ((flags & FLAG_MANUAL_REQUEST) == 0) {
2855                     // Not a manual request
2856                     if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains(
2857                             id)) {
2858                         // Regular autofill handled the view and returned null response, but it
2859                         // triggered augmented autofill
2860                         if (!isSameViewEntered) {
2861                             if (sDebug) Slog.d(TAG, "trigger augmented autofill.");
2862                             triggerAugmentedAutofillLocked(flags);
2863                         } else {
2864                             if (sDebug) {
2865                                 Slog.d(TAG, "skip augmented autofill for same view: "
2866                                         + "same view entered");
2867                             }
2868                         }
2869                         return;
2870                     } else if (mSessionFlags.mAugmentedAutofillOnly && isSameViewEntered) {
2871                         // Regular autofill is disabled.
2872                         if (sDebug) {
2873                             Slog.d(TAG, "skip augmented autofill for same view: "
2874                                     + "standard autofill disabled.");
2875                         }
2876                         return;
2877                     }
2878                 }
2879 
2880                 if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
2881                     return;
2882                 }
2883 
2884                 if (isSameViewEntered) {
2885                     return;
2886                 }
2887 
2888                 // If the ViewState is ready to be displayed, onReady() will be called.
2889                 viewState.update(value, virtualBounds, flags);
2890                 break;
2891             case ACTION_VIEW_EXITED:
2892                 if (Objects.equals(mCurrentViewId, viewState.id)) {
2893                     if (sVerbose) Slog.v(TAG, "Exiting view " + id);
2894                     mUi.hideFillUi(this);
2895                     hideAugmentedAutofillLocked(viewState);
2896                     // We don't send an empty response to IME so that it doesn't cause UI flicker
2897                     // on the IME side if it arrives before the input view is finished on the IME.
2898                     mInlineSessionController.resetInlineFillUiLocked();
2899                     mCurrentViewId = null;
2900                 }
2901                 break;
2902             default:
2903                 Slog.w(TAG, "updateLocked(): unknown action: " + action);
2904         }
2905     }
2906 
2907     @GuardedBy("mLock")
2908     private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) {
2909         if ((viewState.getState()
2910                 & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
2911             viewState.resetState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL);
2912             cancelAugmentedAutofillLocked();
2913         }
2914     }
2915 
2916     /**
2917      * Checks whether a view should be ignored.
2918      */
2919     @GuardedBy("mLock")
2920     private boolean isIgnoredLocked(AutofillId id) {
2921         // Always check the latest response only
2922         final FillResponse response = getLastResponseLocked(null);
2923         if (response == null) return false;
2924 
2925         return ArrayUtils.contains(response.getIgnoredIds(), id);
2926     }
2927 
2928     @GuardedBy("mLock")
2929     private void logIfViewClearedLocked(AutofillId id, AutofillValue value, ViewState viewState) {
2930         if ((value == null || value.isEmpty())
2931                 && viewState.getCurrentValue() != null
2932                 && viewState.getCurrentValue().isText()
2933                 && viewState.getCurrentValue().getTextValue() != null
2934                 && getSaveInfoLocked() != null) {
2935             final int length = viewState.getCurrentValue().getTextValue().length();
2936             if (sDebug) {
2937                 Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
2938                         + length + " chars long");
2939             }
2940             final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
2941                     .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
2942             mMetricsLogger.write(log);
2943         }
2944     }
2945 
2946     @GuardedBy("mLock")
2947     private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value,
2948             ViewState viewState, int flags) {
2949         final String textValue;
2950         if (value == null || !value.isText()) {
2951             textValue = null;
2952         } else {
2953             final CharSequence text = value.getTextValue();
2954             // Text should never be null, but it doesn't hurt to check to avoid a
2955             // system crash...
2956             textValue = (text == null) ? null : text.toString();
2957         }
2958         updateFilteringStateOnValueChangedLocked(textValue, viewState);
2959 
2960         viewState.setCurrentValue(value);
2961 
2962         final String filterText = textValue;
2963 
2964         final AutofillValue filledValue = viewState.getAutofilledValue();
2965         if (filledValue != null) {
2966             if (filledValue.equals(value)) {
2967                 // When the update is caused by autofilling the view, just update the
2968                 // value, not the UI.
2969                 if (sVerbose) {
2970                     Slog.v(TAG, "ignoring autofilled change on id " + id);
2971                 }
2972                 // TODO(b/156099633): remove this once framework gets out of business of resending
2973                 // inline suggestions when IME visibility changes.
2974                 mInlineSessionController.hideInlineSuggestionsUiLocked(viewState.id);
2975                 viewState.resetState(ViewState.STATE_CHANGED);
2976                 return;
2977             } else if ((viewState.id.equals(this.mCurrentViewId))
2978                     && (viewState.getState() & ViewState.STATE_AUTOFILLED) != 0) {
2979                 // Remove autofilled state once field is changed after autofilling.
2980                 if (sVerbose) {
2981                     Slog.v(TAG, "field changed after autofill on id " + id);
2982                 }
2983                 viewState.resetState(ViewState.STATE_AUTOFILLED);
2984                 final ViewState currentView = mViewStates.get(mCurrentViewId);
2985                 currentView.maybeCallOnFillReady(flags);
2986             }
2987         }
2988 
2989         if (viewState.id.equals(this.mCurrentViewId)
2990                 && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
2991             if ((viewState.getState() & ViewState.STATE_INLINE_DISABLED) != 0) {
2992                 mInlineSessionController.disableFilterMatching(viewState.id);
2993             }
2994             mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText);
2995         } else if (viewState.id.equals(this.mCurrentViewId)
2996                 && (viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
2997             if (!TextUtils.isEmpty(filterText)) {
2998                 // TODO: we should be able to replace this with controller#filterInlineFillUiLocked
2999                 // to accomplish filtering for augmented autofill.
3000                 mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
3001             }
3002         }
3003 
3004         viewState.setState(ViewState.STATE_CHANGED);
3005         getUiForShowing().filterFillUi(filterText, this);
3006     }
3007 
3008     /**
3009      * Disable filtering of inline suggestions for further text changes in this view if any
3010      * character was removed earlier and now any character is being added. Such behaviour may
3011      * indicate the IME attempting to probe the potentially sensitive content of inline suggestions.
3012      */
3013     @GuardedBy("mLock")
3014     private void updateFilteringStateOnValueChangedLocked(@Nullable String newTextValue,
3015             ViewState viewState) {
3016         if (newTextValue == null) {
3017             // Don't just return here, otherwise the IME can circumvent this logic using non-text
3018             // values.
3019             newTextValue = "";
3020         }
3021         final AutofillValue currentValue = viewState.getCurrentValue();
3022         final String currentTextValue;
3023         if (currentValue == null || !currentValue.isText()) {
3024             currentTextValue = "";
3025         } else {
3026             currentTextValue = currentValue.getTextValue().toString();
3027         }
3028 
3029         if ((viewState.getState() & ViewState.STATE_CHAR_REMOVED) == 0) {
3030             if (!containsCharsInOrder(newTextValue, currentTextValue)) {
3031                 viewState.setState(ViewState.STATE_CHAR_REMOVED);
3032             }
3033         } else if (!containsCharsInOrder(currentTextValue, newTextValue)) {
3034             // Characters were added or replaced.
3035             viewState.setState(ViewState.STATE_INLINE_DISABLED);
3036         }
3037     }
3038 
3039     @Override
3040     public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
3041             @Nullable AutofillValue value) {
3042         synchronized (mLock) {
3043             if (mDestroyed) {
3044                 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
3045                         + id + " destroyed");
3046                 return;
3047             }
3048         }
3049 
3050         String filterText = null;
3051         if (value != null && value.isText()) {
3052             filterText = value.getTextValue().toString();
3053         }
3054 
3055         final CharSequence serviceLabel;
3056         final Drawable serviceIcon;
3057         synchronized (mLock) {
3058             serviceLabel = mService.getServiceLabelLocked();
3059             serviceIcon = mService.getServiceIconLocked();
3060         }
3061         if (serviceLabel == null || serviceIcon == null) {
3062             wtf(null, "onFillReady(): no service label or icon");
3063             return;
3064         }
3065 
3066         if (response.supportsInlineSuggestions()) {
3067             synchronized (mLock) {
3068                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
3069                     final ViewState currentView = mViewStates.get(mCurrentViewId);
3070                     currentView.setState(ViewState.STATE_INLINE_SHOWN);
3071                     //TODO(b/137800469): Fix it to log showed only when IME asks for inflation,
3072                     // rather than here where framework sends back the response.
3073                     mService.logDatasetShown(id, mClientState);
3074                     return;
3075                 }
3076             }
3077         }
3078 
3079         getUiForShowing().showFillUi(filledId, response, filterText,
3080                 mService.getServicePackageName(), mComponentName,
3081                 serviceLabel, serviceIcon, this, id, mCompatMode);
3082 
3083         mService.logDatasetShown(id, mClientState);
3084 
3085         synchronized (mLock) {
3086             if (mUiShownTime == 0) {
3087                 // Log first time UI is shown.
3088                 mUiShownTime = SystemClock.elapsedRealtime();
3089                 final long duration = mUiShownTime - mStartTime;
3090                 if (sDebug) {
3091                     final StringBuilder msg = new StringBuilder("1st UI for ")
3092                             .append(mActivityToken)
3093                             .append(" shown in ");
3094                     TimeUtils.formatDuration(duration, msg);
3095                     Slog.d(TAG, msg.toString());
3096                 }
3097                 final StringBuilder historyLog = new StringBuilder("id=").append(id)
3098                         .append(" app=").append(mActivityToken)
3099                         .append(" svc=").append(mService.getServicePackageName())
3100                         .append(" latency=");
3101                 TimeUtils.formatDuration(duration, historyLog);
3102                 mUiLatencyHistory.log(historyLog.toString());
3103 
3104                 addTaggedDataToRequestLogLocked(response.getRequestId(),
3105                         MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
3106             }
3107         }
3108     }
3109 
3110     /**
3111      * Returns whether we made a request to show inline suggestions.
3112      */
3113     private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
3114             @Nullable String filterText) {
3115         if (mCurrentViewId == null) {
3116             Log.w(TAG, "requestShowInlineSuggestionsLocked(): no view currently focused");
3117             return false;
3118         }
3119         final AutofillId focusedId = mCurrentViewId;
3120 
3121         final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
3122                 mInlineSessionController.getInlineSuggestionsRequestLocked();
3123         if (!inlineSuggestionsRequest.isPresent()) {
3124             Log.w(TAG, "InlineSuggestionsRequest unavailable");
3125             return false;
3126         }
3127 
3128         final RemoteInlineSuggestionRenderService remoteRenderService =
3129                 mService.getRemoteInlineSuggestionRenderServiceLocked();
3130         if (remoteRenderService == null) {
3131             Log.w(TAG, "RemoteInlineSuggestionRenderService not found");
3132             return false;
3133         }
3134 
3135         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
3136                 new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
3137                         filterText, remoteRenderService, userId, id);
3138         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
3139                 new InlineFillUi.InlineSuggestionUiCallback() {
3140                     @Override
3141                     public void autofill(@NonNull Dataset dataset, int datasetIndex) {
3142                         fill(response.getRequestId(), datasetIndex, dataset);
3143                     }
3144 
3145                     @Override
3146                     public void authenticate(int requestId, int datasetIndex) {
3147                         Session.this.authenticate(response.getRequestId(), datasetIndex,
3148                                 response.getAuthentication(), response.getClientState(),
3149                                 /* authenticateInline= */ true);
3150                     }
3151 
3152                     @Override
3153                     public void startIntentSender(@NonNull IntentSender intentSender) {
3154                         Session.this.startIntentSender(intentSender, new Intent());
3155                     }
3156 
3157                     @Override
3158                     public void onError() {
3159                         synchronized (mLock) {
3160                             mInlineSessionController.setInlineFillUiLocked(
3161                                     InlineFillUi.emptyUi(focusedId));
3162                         }
3163                     }
3164                 });
3165         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
3166     }
3167 
3168     boolean isDestroyed() {
3169         synchronized (mLock) {
3170             return mDestroyed;
3171         }
3172     }
3173 
3174     IAutoFillManagerClient getClient() {
3175         synchronized (mLock) {
3176             return mClient;
3177         }
3178     }
3179 
3180     private void notifyUnavailableToClient(int sessionFinishedState,
3181             @Nullable ArrayList<AutofillId> autofillableIds) {
3182         synchronized (mLock) {
3183             if (mCurrentViewId == null) return;
3184             try {
3185                 if (mHasCallback) {
3186                     mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
3187                 } else if (sessionFinishedState != AutofillManager.STATE_UNKNOWN) {
3188                     mClient.setSessionFinished(sessionFinishedState, autofillableIds);
3189                 }
3190             } catch (RemoteException e) {
3191                 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
3192             }
3193         }
3194     }
3195 
3196     private void notifyDisableAutofillToClient(long disableDuration, ComponentName componentName) {
3197         synchronized (mLock) {
3198             if (mCurrentViewId == null) return;
3199             try {
3200                 mClient.notifyDisableAutofill(disableDuration, componentName);
3201             } catch (RemoteException e) {
3202                 Slog.e(TAG, "Error notifying client disable autofill: id=" + mCurrentViewId, e);
3203             }
3204         }
3205     }
3206 
3207     @GuardedBy("mLock")
3208     private void updateTrackedIdsLocked() {
3209         // Only track the views of the last response as only those are reported back to the
3210         // service, see #showSaveLocked
3211         final FillResponse response = getLastResponseLocked(null);
3212         if (response == null) return;
3213 
3214         ArraySet<AutofillId> trackedViews = null;
3215         mSaveOnAllViewsInvisible = false;
3216         boolean saveOnFinish = true;
3217         final SaveInfo saveInfo = response.getSaveInfo();
3218         final AutofillId saveTriggerId;
3219         final int flags;
3220         if (saveInfo != null) {
3221             saveTriggerId = saveInfo.getTriggerId();
3222             if (saveTriggerId != null) {
3223                 writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
3224             }
3225             flags = saveInfo.getFlags();
3226             mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
3227 
3228             // We only need to track views if we want to save once they become invisible.
3229             if (mSaveOnAllViewsInvisible) {
3230                 if (trackedViews == null) {
3231                     trackedViews = new ArraySet<>();
3232                 }
3233                 if (saveInfo.getRequiredIds() != null) {
3234                     Collections.addAll(trackedViews, saveInfo.getRequiredIds());
3235                 }
3236 
3237                 if (saveInfo.getOptionalIds() != null) {
3238                     Collections.addAll(trackedViews, saveInfo.getOptionalIds());
3239                 }
3240             }
3241             if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
3242                 saveOnFinish = false;
3243             }
3244 
3245         } else {
3246             flags = 0;
3247             saveTriggerId = null;
3248         }
3249 
3250         // Must also track that are part of datasets, otherwise the FillUI won't be hidden when
3251         // they go away (if they're not savable).
3252 
3253         final List<Dataset> datasets = response.getDatasets();
3254         ArraySet<AutofillId> fillableIds = null;
3255         if (datasets != null) {
3256             for (int i = 0; i < datasets.size(); i++) {
3257                 final Dataset dataset = datasets.get(i);
3258                 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
3259                 if (fieldIds == null) continue;
3260 
3261                 for (int j = 0; j < fieldIds.size(); j++) {
3262                     final AutofillId id = fieldIds.get(j);
3263                     if (trackedViews == null || !trackedViews.contains(id)) {
3264                         fillableIds = ArrayUtils.add(fillableIds, id);
3265                     }
3266                 }
3267             }
3268         }
3269 
3270         try {
3271             if (sVerbose) {
3272                 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
3273                         + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish
3274                         + " flags: " + flags + " hasSaveInfo: " + (saveInfo != null));
3275             }
3276             mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
3277                     saveOnFinish, toArray(fillableIds), saveTriggerId);
3278         } catch (RemoteException e) {
3279             Slog.w(TAG, "Cannot set tracked ids", e);
3280         }
3281     }
3282 
3283     /**
3284      * Sets the state of views that failed to autofill.
3285      */
3286     @GuardedBy("mLock")
3287     void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
3288         for (int i = 0; i < ids.size(); i++) {
3289             final AutofillId id = ids.get(i);
3290             final ViewState viewState = mViewStates.get(id);
3291             if (viewState == null) {
3292                 Slog.w(TAG, "setAutofillFailure(): no view for id " + id);
3293                 continue;
3294             }
3295             viewState.resetState(ViewState.STATE_AUTOFILLED);
3296             final int state = viewState.getState();
3297             viewState.setState(state | ViewState.STATE_AUTOFILL_FAILED);
3298             if (sVerbose) {
3299                 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
3300             }
3301         }
3302     }
3303 
3304     @GuardedBy("mLock")
3305     private void replaceResponseLocked(@NonNull FillResponse oldResponse,
3306             @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
3307         // Disassociate view states with the old response
3308         setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
3309         // Move over the id
3310         newResponse.setRequestId(oldResponse.getRequestId());
3311         // Replace the old response
3312         mResponses.put(newResponse.getRequestId(), newResponse);
3313         // Now process the new response
3314         processResponseLocked(newResponse, newClientState, 0);
3315     }
3316 
3317     @GuardedBy("mLock")
3318     private void processNullResponseLocked(int requestId, int flags) {
3319         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
3320             getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
3321         }
3322 
3323         final FillContext context = getFillContextByRequestIdLocked(requestId);
3324 
3325         final ArrayList<AutofillId> autofillableIds;
3326         if (context != null) {
3327             final AssistStructure structure = context.getStructure();
3328             autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */true);
3329         } else {
3330             Slog.w(TAG, "processNullResponseLocked(): no context for req " + requestId);
3331             autofillableIds = null;
3332         }
3333 
3334         mService.resetLastResponse();
3335 
3336         // The default autofill service cannot fullfill the request, let's check if the augmented
3337         // autofill service can.
3338         mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags);
3339         if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) {
3340             if (sVerbose) {
3341                 Slog.v(TAG, "canceling session " + id + " when service returned null and it cannot "
3342                         + "be augmented. AutofillableIds: " + autofillableIds);
3343             }
3344             // Nothing to be done, but need to notify client.
3345             notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds);
3346             removeFromService();
3347         } else {
3348             if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) {
3349                 if (sVerbose) {
3350                     Slog.v(TAG, "keeping session " + id + " when service returned null and "
3351                             + "augmented service is disabled for password fields. "
3352                             + "AutofillableIds: " + autofillableIds);
3353                 }
3354                 mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
3355             } else {
3356                 if (sVerbose) {
3357                     Slog.v(TAG, "keeping session " + id + " when service returned null but "
3358                             + "it can be augmented. AutofillableIds: " + autofillableIds);
3359                 }
3360             }
3361             mAugmentedAutofillableIds = autofillableIds;
3362             try {
3363                 mClient.setState(AutofillManager.SET_STATE_FLAG_FOR_AUTOFILL_ONLY);
3364             } catch (RemoteException e) {
3365                 Slog.e(TAG, "Error setting client to autofill-only", e);
3366             }
3367         }
3368     }
3369 
3370     /**
3371      * Tries to trigger Augmented Autofill when the standard service could not fulfill a request.
3372      *
3373      * <p> The request may not have been sent when this method returns as it may be waiting for
3374      * the inline suggestion request asynchronously.
3375      *
3376      * @return callback to destroy the autofill UI, or {@code null} if not supported.
3377      */
3378     // TODO(b/123099468): might need to call it in other places, like when the service returns a
3379     // non-null response but without datasets (for example, just SaveInfo)
3380     @GuardedBy("mLock")
3381     private Runnable triggerAugmentedAutofillLocked(int flags) {
3382         // TODO: (b/141703197) Fix later by passing info to service.
3383         if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) {
3384             return null;
3385         }
3386 
3387         // Check if Smart Suggestions is supported...
3388         final @SmartSuggestionMode int supportedModes = mService
3389                 .getSupportedSmartSuggestionModesLocked();
3390         if (supportedModes == 0) {
3391             if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no supported modes");
3392             return null;
3393         }
3394 
3395         // ...then if the service is set for the user
3396 
3397         final RemoteAugmentedAutofillService remoteService = mService
3398                 .getRemoteAugmentedAutofillServiceLocked();
3399         if (remoteService == null) {
3400             if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user");
3401             return null;
3402         }
3403 
3404         // Define which mode will be used
3405         final int mode;
3406         if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
3407             mode = FLAG_SMART_SUGGESTION_SYSTEM;
3408         } else {
3409             Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes);
3410             return null;
3411         }
3412 
3413         if (mCurrentViewId == null) {
3414             Slog.w(TAG, "triggerAugmentedAutofillLocked(): no view currently focused");
3415             return null;
3416         }
3417 
3418         final boolean isWhitelisted = mService
3419                 .isWhitelistedForAugmentedAutofillLocked(mComponentName);
3420 
3421         if (!isWhitelisted) {
3422             if (sVerbose) {
3423                 Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
3424                         + ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
3425             }
3426             logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
3427                     mCurrentViewId, isWhitelisted, /* isInline= */ null);
3428             return null;
3429         }
3430 
3431         if (sVerbose) {
3432             Slog.v(TAG, "calling Augmented Autofill Service ("
3433                     + ComponentName.flattenToShortString(remoteService.getComponentName())
3434                     + ") on view " + mCurrentViewId + " using suggestion mode "
3435                     + getSmartSuggestionModeToString(mode)
3436                     + " when server returned null for session " + this.id);
3437         }
3438 
3439         final ViewState viewState = mViewStates.get(mCurrentViewId);
3440         viewState.setState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL);
3441         final AutofillValue currentValue = viewState.getCurrentValue();
3442 
3443         if (mAugmentedRequestsLogs == null) {
3444             mAugmentedRequestsLogs = new ArrayList<>();
3445         }
3446         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
3447                 remoteService.getComponentName().getPackageName());
3448         mAugmentedRequestsLogs.add(log);
3449 
3450         final AutofillId focusedId = mCurrentViewId;
3451 
3452         final Function<InlineFillUi, Boolean> inlineSuggestionsResponseCallback =
3453                 response -> {
3454                     synchronized (mLock) {
3455                         return mInlineSessionController.setInlineFillUiLocked(response);
3456                     }
3457                 };
3458         final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
3459                 (inlineSuggestionsRequest) -> {
3460                     synchronized (mLock) {
3461                         logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
3462                                 focusedId, isWhitelisted, inlineSuggestionsRequest != null);
3463                         remoteService.onRequestAutofillLocked(id, mClient,
3464                                 taskId, mComponentName, mActivityToken,
3465                                 AutofillId.withoutSession(focusedId), currentValue,
3466                                 inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
3467                                 /*onErrorCallback=*/ () -> {
3468                                     synchronized (mLock) {
3469                                         cancelAugmentedAutofillLocked();
3470 
3471                                         // Also cancel augmented in IME
3472                                         mInlineSessionController.setInlineFillUiLocked(
3473                                                 InlineFillUi.emptyUi(mCurrentViewId));
3474                                     }
3475                                 }, mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
3476                     }
3477                 };
3478 
3479         // When the inline suggestion render service is available and the view is focused, there
3480         // are 3 cases when augmented autofill should ask IME for inline suggestion request,
3481         // because standard autofill flow didn't:
3482         // 1. the field is augmented autofill only (when standard autofill provider is None or
3483         // when it returns null response)
3484         // 2. standard autofill provider doesn't support inline suggestion
3485         // 3. we re-entered the autofill session and standard autofill was not re-triggered, this is
3486         //    recognized by seeing mExpiredResponse == true
3487         final RemoteInlineSuggestionRenderService remoteRenderService =
3488                 mService.getRemoteInlineSuggestionRenderServiceLocked();
3489         if (remoteRenderService != null
3490                 && (mSessionFlags.mAugmentedAutofillOnly
3491                 || !mSessionFlags.mInlineSupportedByService
3492                 || mSessionFlags.mExpiredResponse)
3493                 && isViewFocusedLocked(flags)) {
3494             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
3495             remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
3496                     (extras) -> {
3497                         synchronized (mLock) {
3498                             mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
3499                                     focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
3500                                     extras);
3501                         }
3502                     }, mHandler));
3503         } else {
3504             requestAugmentedAutofill.accept(
3505                     mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
3506         }
3507         if (mAugmentedAutofillDestroyer == null) {
3508             mAugmentedAutofillDestroyer = remoteService::onDestroyAutofillWindowsRequest;
3509         }
3510         return mAugmentedAutofillDestroyer;
3511     }
3512 
3513     @GuardedBy("mLock")
3514     private void cancelAugmentedAutofillLocked() {
3515         final RemoteAugmentedAutofillService remoteService = mService
3516                 .getRemoteAugmentedAutofillServiceLocked();
3517         if (remoteService == null) {
3518             Slog.w(TAG, "cancelAugmentedAutofillLocked(): no service for user");
3519             return;
3520         }
3521         if (sVerbose) Slog.v(TAG, "cancelAugmentedAutofillLocked() on " + mCurrentViewId);
3522         remoteService.onDestroyAutofillWindowsRequest();
3523     }
3524 
3525     @GuardedBy("mLock")
3526     private void processResponseLocked(@NonNull FillResponse newResponse,
3527             @Nullable Bundle newClientState, int flags) {
3528         // Make sure we are hiding the UI which will be shown
3529         // only if handling the current response requires it.
3530         mUi.hideAll(this);
3531 
3532         final int requestId = newResponse.getRequestId();
3533         if (sVerbose) {
3534             Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
3535                     + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
3536                     + ",newClientState=" + newClientState);
3537         }
3538 
3539         if (mResponses == null) {
3540             // Set initial capacity as 2 to handle cases where service always requires auth.
3541             // TODO: add a metric for number of responses set by server, so we can use its average
3542             // as the initial array capacitiy.
3543             mResponses = new SparseArray<>(2);
3544         }
3545         mResponses.put(requestId, newResponse);
3546         mClientState = newClientState != null ? newClientState : newResponse.getClientState();
3547 
3548         setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
3549         updateTrackedIdsLocked();
3550 
3551         if (mCurrentViewId == null) {
3552             return;
3553         }
3554 
3555         // Updates the UI, if necessary.
3556         final ViewState currentView = mViewStates.get(mCurrentViewId);
3557         currentView.maybeCallOnFillReady(flags);
3558     }
3559 
3560     /**
3561      * Sets the state of all views in the given response.
3562      */
3563     @GuardedBy("mLock")
3564     private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
3565         final List<Dataset> datasets = response.getDatasets();
3566         if (datasets != null) {
3567             for (int i = 0; i < datasets.size(); i++) {
3568                 final Dataset dataset = datasets.get(i);
3569                 if (dataset == null) {
3570                     Slog.w(TAG, "Ignoring null dataset on " + datasets);
3571                     continue;
3572                 }
3573                 setViewStatesLocked(response, dataset, state, clearResponse);
3574             }
3575         } else if (response.getAuthentication() != null) {
3576             for (AutofillId autofillId : response.getAuthenticationIds()) {
3577                 final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null);
3578                 if (!clearResponse) {
3579                     viewState.setResponse(response);
3580                 } else {
3581                     viewState.setResponse(null);
3582                 }
3583             }
3584         }
3585         final SaveInfo saveInfo = response.getSaveInfo();
3586         if (saveInfo != null) {
3587             final AutofillId[] requiredIds = saveInfo.getRequiredIds();
3588             if (requiredIds != null) {
3589                 for (AutofillId id : requiredIds) {
3590                     createOrUpdateViewStateLocked(id, state, null);
3591                 }
3592             }
3593             final AutofillId[] optionalIds = saveInfo.getOptionalIds();
3594             if (optionalIds != null) {
3595                 for (AutofillId id : optionalIds) {
3596                     createOrUpdateViewStateLocked(id, state, null);
3597                 }
3598             }
3599         }
3600 
3601         final AutofillId[] authIds = response.getAuthenticationIds();
3602         if (authIds != null) {
3603             for (AutofillId id : authIds) {
3604                 createOrUpdateViewStateLocked(id, state, null);
3605             }
3606         }
3607     }
3608 
3609     /**
3610      * Sets the state and response of all views in the given dataset.
3611      */
3612     @GuardedBy("mLock")
3613     private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
3614             int state, boolean clearResponse) {
3615         final ArrayList<AutofillId> ids = dataset.getFieldIds();
3616         final ArrayList<AutofillValue> values = dataset.getFieldValues();
3617         for (int j = 0; j < ids.size(); j++) {
3618             final AutofillId id = ids.get(j);
3619             final AutofillValue value = values.get(j);
3620             final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
3621             final String datasetId = dataset.getId();
3622             if (datasetId != null) {
3623                 viewState.setDatasetId(datasetId);
3624             }
3625             if (clearResponse) {
3626                 viewState.setResponse(null);
3627             } else if (response != null) {
3628                 viewState.setResponse(response);
3629             }
3630         }
3631     }
3632 
3633     @GuardedBy("mLock")
3634     private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
3635             @Nullable AutofillValue value) {
3636         ViewState viewState = mViewStates.get(id);
3637         if (viewState != null)  {
3638             viewState.setState(state);
3639         } else {
3640             viewState = new ViewState(id, this, state);
3641             if (sVerbose) {
3642                 Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state);
3643             }
3644             viewState.setCurrentValue(findValueLocked(id));
3645             mViewStates.put(id, viewState);
3646         }
3647         if ((state & ViewState.STATE_AUTOFILLED) != 0) {
3648             viewState.setAutofilledValue(value);
3649         }
3650         return viewState;
3651     }
3652 
3653     void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
3654         if (sDebug) {
3655             Slog.d(TAG, "autoFill(): requestId=" + requestId  + "; datasetIdx=" + datasetIndex
3656                     + "; dataset=" + dataset);
3657         }
3658         synchronized (mLock) {
3659             if (mDestroyed) {
3660                 Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
3661                         + id + " destroyed");
3662                 return;
3663             }
3664             // Autofill it directly...
3665             if (dataset.getAuthentication() == null) {
3666                 if (generateEvent) {
3667                     mService.logDatasetSelected(dataset.getId(), id, mClientState);
3668                 }
3669                 if (mCurrentViewId != null) {
3670                     mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
3671                 }
3672                 autoFillApp(dataset);
3673                 return;
3674             }
3675 
3676             // ...or handle authentication.
3677             mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
3678             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
3679             final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
3680             if (fillInIntent == null) {
3681                 forceRemoveFromServiceLocked();
3682                 return;
3683             }
3684             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
3685                     datasetIndex);
3686             startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent,
3687                     /* authenticateInline= */false);
3688 
3689         }
3690     }
3691 
3692     // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
3693     @GuardedBy("mLock")
3694     @Nullable
3695     private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
3696         final Intent fillInIntent = new Intent();
3697 
3698         final FillContext context = getFillContextByRequestIdLocked(requestId);
3699 
3700         if (context == null) {
3701             wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s",
3702                     requestId, mContexts);
3703             return null;
3704         }
3705         if (mLastInlineSuggestionsRequest != null
3706                 && mLastInlineSuggestionsRequest.first == requestId) {
3707             fillInIntent.putExtra(AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST,
3708                     mLastInlineSuggestionsRequest.second);
3709         }
3710         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
3711         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
3712         return fillInIntent;
3713     }
3714 
3715     @NonNull
3716     private Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestCacheDecorator(
3717             @NonNull Consumer<InlineSuggestionsRequest> consumer, int requestId) {
3718         return inlineSuggestionsRequest -> {
3719             consumer.accept(inlineSuggestionsRequest);
3720             synchronized (mLock) {
3721                 mLastInlineSuggestionsRequest = Pair.create(requestId, inlineSuggestionsRequest);
3722             }
3723         };
3724     }
3725 
3726     private void startAuthentication(int authenticationId, IntentSender intent,
3727             Intent fillInIntent, boolean authenticateInline) {
3728         try {
3729             synchronized (mLock) {
3730                 mClient.authenticate(id, authenticationId, intent, fillInIntent,
3731                         authenticateInline);
3732             }
3733         } catch (RemoteException e) {
3734             Slog.e(TAG, "Error launching auth intent", e);
3735         }
3736     }
3737 
3738     /**
3739      * The result of checking whether to show the save dialog, when session can be saved.
3740      *
3741      * @hide
3742      */
3743     static final class SaveResult {
3744         /**
3745          * Whether to record the save dialog has been shown.
3746          */
3747         private boolean mLogSaveShown;
3748 
3749         /**
3750          * Whether to remove the session.
3751          */
3752         private boolean mRemoveSession;
3753 
3754         /**
3755          * The reason why a save dialog was not shown.
3756          */
3757         @NoSaveReason private int mSaveDialogNotShowReason;
3758 
3759         SaveResult(boolean logSaveShown, boolean removeSession,
3760                 @NoSaveReason int saveDialogNotShowReason) {
3761             mLogSaveShown = logSaveShown;
3762             mRemoveSession = removeSession;
3763             mSaveDialogNotShowReason = saveDialogNotShowReason;
3764         }
3765 
3766         /**
3767          * Returns whether to record the save dialog has been shown.
3768          *
3769          * @return Whether to record the save dialog has been shown.
3770          */
3771         public boolean isLogSaveShown() {
3772             return mLogSaveShown;
3773         }
3774 
3775         /**
3776          * Sets whether to record the save dialog has been shown.
3777          *
3778          * @param logSaveShown Whether to record the save dialog has been shown.
3779          */
3780         public void setLogSaveShown(boolean logSaveShown) {
3781             mLogSaveShown = logSaveShown;
3782         }
3783 
3784         /**
3785          * Returns whether to remove the session.
3786          *
3787          * @return Whether to remove the session.
3788          */
3789         public boolean isRemoveSession() {
3790             return mRemoveSession;
3791         }
3792 
3793         /**
3794          * Sets whether to remove the session.
3795          *
3796          * @param removeSession Whether to remove the session.
3797          */
3798         public void setRemoveSession(boolean removeSession) {
3799             mRemoveSession = removeSession;
3800         }
3801 
3802         /**
3803          * Returns the reason why a save dialog was not shown.
3804          *
3805          * @return The reason why a save dialog was not shown.
3806          */
3807         @NoSaveReason
3808         public int getNoSaveUiReason() {
3809             return mSaveDialogNotShowReason;
3810         }
3811 
3812         /**
3813          * Sets the reason why a save dialog was not shown.
3814          *
3815          * @param saveDialogNotShowReason The reason why a save dialog was not shown.
3816          */
3817         public void setSaveDialogNotShowReason(@NoSaveReason int saveDialogNotShowReason) {
3818             mSaveDialogNotShowReason = saveDialogNotShowReason;
3819         }
3820 
3821         @Override
3822         public String toString() {
3823             return "SaveResult: [logSaveShown=" + mLogSaveShown
3824                     + ", removeSession=" + mRemoveSession
3825                     + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason + "]";
3826         }
3827     }
3828 
3829     @Override
3830     public String toString() {
3831         return "Session: [id=" + id + ", component=" + mComponentName
3832                 + ", state=" + sessionStateAsString(mSessionState) + "]";
3833     }
3834 
3835     @GuardedBy("mLock")
3836     void dumpLocked(String prefix, PrintWriter pw) {
3837         final String prefix2 = prefix + "  ";
3838         pw.print(prefix); pw.print("id: "); pw.println(id);
3839         pw.print(prefix); pw.print("uid: "); pw.println(uid);
3840         pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
3841         pw.print(prefix); pw.print("flags: "); pw.println(mFlags);
3842         pw.print(prefix); pw.print("state: "); pw.println(sessionStateAsString(mSessionState));
3843         pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
3844         pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
3845         pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
3846         pw.print(prefix); pw.print("Time to show UI: ");
3847         if (mUiShownTime == 0) {
3848             pw.println("N/A");
3849         } else {
3850             TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
3851             pw.println();
3852         }
3853         final int requestLogsSizes = mRequestLogs.size();
3854         pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes);
3855         for (int i = 0; i < requestLogsSizes; i++) {
3856             final int requestId = mRequestLogs.keyAt(i);
3857             final LogMaker log = mRequestLogs.valueAt(i);
3858             pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req=");
3859             pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println();
3860         }
3861         pw.print(prefix); pw.print("mResponses: ");
3862         if (mResponses == null) {
3863             pw.println("null");
3864         } else {
3865             pw.println(mResponses.size());
3866             for (int i = 0; i < mResponses.size(); i++) {
3867                 pw.print(prefix2); pw.print('#'); pw.print(i);
3868                 pw.print(' '); pw.println(mResponses.valueAt(i));
3869             }
3870         }
3871         pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
3872         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
3873         pw.print(prefix); pw.print("mShowingSaveUi: "); pw.println(mSessionFlags.mShowingSaveUi);
3874         pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi);
3875         final int numberViews = mViewStates.size();
3876         pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
3877         for (int i = 0; i < numberViews; i++) {
3878             pw.print(prefix); pw.print("ViewState at #"); pw.println(i);
3879             mViewStates.valueAt(i).dump(prefix2, pw);
3880         }
3881 
3882         pw.print(prefix); pw.print("mContexts: " );
3883         if (mContexts != null) {
3884             int numContexts = mContexts.size();
3885             for (int i = 0; i < numContexts; i++) {
3886                 FillContext context = mContexts.get(i);
3887 
3888                 pw.print(prefix2); pw.print(context);
3889                 if (sVerbose) {
3890                     pw.println("AssistStructure dumped at logcat)");
3891 
3892                     // TODO: add method on AssistStructure to dump on pw
3893                     context.getStructure().dump(false);
3894                 }
3895             }
3896         } else {
3897             pw.println("null");
3898         }
3899 
3900         pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
3901         if (mClientState != null) {
3902             pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw
3903                 .println(" bytes");
3904         }
3905         pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode);
3906         pw.print(prefix); pw.print("mUrlBar: ");
3907         if (mUrlBar == null) {
3908             pw.println("N/A");
3909         } else {
3910             pw.print("id="); pw.print(mUrlBar.getAutofillId());
3911             pw.print(" domain="); pw.print(mUrlBar.getWebDomain());
3912             pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText());
3913         }
3914         pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println(
3915                 mSaveOnAllViewsInvisible);
3916         pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds);
3917         if (mSessionFlags.mAugmentedAutofillOnly) {
3918             pw.print(prefix); pw.println("For Augmented Autofill Only");
3919         }
3920         if (mAugmentedAutofillDestroyer != null) {
3921             pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
3922         }
3923         if (mAugmentedRequestsLogs != null) {
3924             pw.print(prefix); pw.print("number augmented requests: ");
3925             pw.println(mAugmentedRequestsLogs.size());
3926         }
3927 
3928         if (mAugmentedAutofillableIds != null) {
3929             pw.print(prefix); pw.print("mAugmentedAutofillableIds: ");
3930             pw.println(mAugmentedAutofillableIds);
3931         }
3932         if (mRemoteFillService != null) {
3933             mRemoteFillService.dump(prefix, pw);
3934         }
3935     }
3936 
3937     private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) {
3938         pw.print("CAT="); pw.print(log.getCategory());
3939         pw.print(", TYPE=");
3940         final int type = log.getType();
3941         switch (type) {
3942             case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break;
3943             case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break;
3944             case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break;
3945             default: pw.print("UNSUPPORTED");
3946         }
3947         pw.print('('); pw.print(type); pw.print(')');
3948         pw.print(", PKG="); pw.print(log.getPackageName());
3949         pw.print(", SERVICE="); pw.print(log
3950                 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE));
3951         pw.print(", ORDINAL="); pw.print(log
3952                 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL));
3953         dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS);
3954         dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS);
3955         dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION);
3956         final int authStatus =
3957                 getNumericValue(log, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS);
3958         if (authStatus != 0) {
3959             pw.print(", AUTH_STATUS=");
3960             switch (authStatus) {
3961                 case MetricsEvent.AUTOFILL_AUTHENTICATED:
3962                     pw.print("AUTHENTICATED"); break;
3963                 case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED:
3964                     pw.print("DATASET_AUTHENTICATED"); break;
3965                 case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION:
3966                     pw.print("INVALID_AUTHENTICATION"); break;
3967                 case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION:
3968                     pw.print("INVALID_DATASET_AUTHENTICATION"); break;
3969                 default: pw.print("UNSUPPORTED");
3970             }
3971             pw.print('('); pw.print(authStatus); pw.print(')');
3972         }
3973         dumpNumericValue(pw, log, "FC_IDS",
3974                 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS);
3975         dumpNumericValue(pw, log, "COMPAT_MODE",
3976                 MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE);
3977     }
3978 
3979     private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log,
3980             @NonNull String field, int tag) {
3981         final int value = getNumericValue(log, tag);
3982         if (value != 0) {
3983             pw.print(", "); pw.print(field); pw.print('='); pw.print(value);
3984         }
3985     }
3986 
3987     void autoFillApp(Dataset dataset) {
3988         synchronized (mLock) {
3989             if (mDestroyed) {
3990                 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: "
3991                         + id + " destroyed");
3992                 return;
3993             }
3994             try {
3995                 // Skip null values as a null values means no change
3996                 final int entryCount = dataset.getFieldIds().size();
3997                 final List<AutofillId> ids = new ArrayList<>(entryCount);
3998                 final List<AutofillValue> values = new ArrayList<>(entryCount);
3999                 boolean waitingDatasetAuth = false;
4000                 boolean hideHighlight = (entryCount == 1
4001                         && dataset.getFieldIds().get(0).equals(mCurrentViewId));
4002                 for (int i = 0; i < entryCount; i++) {
4003                     if (dataset.getFieldValues().get(i) == null) {
4004                         continue;
4005                     }
4006                     final AutofillId viewId = dataset.getFieldIds().get(i);
4007                     ids.add(viewId);
4008                     values.add(dataset.getFieldValues().get(i));
4009                     final ViewState viewState = mViewStates.get(viewId);
4010                     if (viewState != null
4011                             && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) {
4012                         if (sVerbose) {
4013                             Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth");
4014                         }
4015                         waitingDatasetAuth = true;
4016                         viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH);
4017                     }
4018                 }
4019                 if (!ids.isEmpty()) {
4020                     if (waitingDatasetAuth) {
4021                         mUi.hideFillUi(this);
4022                     }
4023                     if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
4024 
4025                     mClient.autofill(id, ids, values, hideHighlight);
4026                     if (dataset.getId() != null) {
4027                         if (mSelectedDatasetIds == null) {
4028                             mSelectedDatasetIds = new ArrayList<>();
4029                         }
4030                         mSelectedDatasetIds.add(dataset.getId());
4031                     }
4032                     setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false);
4033                 }
4034             } catch (RemoteException e) {
4035                 Slog.w(TAG, "Error autofilling activity: " + e);
4036             }
4037         }
4038     }
4039 
4040     private AutoFillUI getUiForShowing() {
4041         synchronized (mLock) {
4042             mUi.setCallback(this);
4043             return mUi;
4044         }
4045     }
4046 
4047     /**
4048      * Destroy this session and perform any clean up work.
4049      *
4050      * <p>Typically called in 2 scenarios:
4051      *
4052      * <ul>
4053      *   <li>When the session naturally finishes (i.e., from {@link #removeFromServiceLocked()}.
4054      *   <li>When the service hosting the session is finished (for example, because the user
4055      *       disabled it).
4056      * </ul>
4057      */
4058     @GuardedBy("mLock")
4059     RemoteFillService destroyLocked() {
4060         if (mDestroyed) {
4061             return null;
4062         }
4063 
4064         unlinkClientVultureLocked();
4065         mUi.destroyAll(mPendingSaveUi, this, true);
4066         mUi.clearCallback(this);
4067         if (mCurrentViewId != null) {
4068             mInlineSessionController.destroyLocked(mCurrentViewId);
4069         }
4070         final RemoteInlineSuggestionRenderService remoteRenderService =
4071                 mService.getRemoteInlineSuggestionRenderServiceLocked();
4072         if (remoteRenderService != null) {
4073             remoteRenderService.destroySuggestionViews(userId, id);
4074         }
4075 
4076         mDestroyed = true;
4077 
4078         // Log metrics
4079         final int totalRequests = mRequestLogs.size();
4080         if (totalRequests > 0) {
4081             if (sVerbose) Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " requests");
4082             for (int i = 0; i < totalRequests; i++) {
4083                 final LogMaker log = mRequestLogs.valueAt(i);
4084                 mMetricsLogger.write(log);
4085             }
4086         }
4087 
4088         final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0
4089                 : mAugmentedRequestsLogs.size();
4090         if (totalAugmentedRequests > 0) {
4091             if (sVerbose) {
4092                 Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests");
4093             }
4094             for (int i = 0; i < totalAugmentedRequests; i++) {
4095                 final LogMaker log = mAugmentedRequestsLogs.get(i);
4096                 mMetricsLogger.write(log);
4097             }
4098         }
4099 
4100         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
4101                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
4102         if (totalAugmentedRequests > 0) {
4103             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
4104                     totalAugmentedRequests);
4105         }
4106         if (mSessionFlags.mAugmentedAutofillOnly) {
4107             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_AUGMENTED_ONLY, 1);
4108         }
4109         mMetricsLogger.write(log);
4110 
4111         return mRemoteFillService;
4112     }
4113 
4114     /**
4115      * Destroy this session and remove it from the service always, even if it does have a pending
4116      * Save UI.
4117      */
4118     @GuardedBy("mLock")
4119     void forceRemoveFromServiceLocked() {
4120         forceRemoveFromServiceLocked(AutofillManager.STATE_UNKNOWN);
4121     }
4122 
4123     @GuardedBy("mLock")
4124     void forceRemoveFromServiceIfForAugmentedOnlyLocked() {
4125         if (sVerbose) {
4126             Slog.v(TAG, "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + this.id + "): "
4127                     + mSessionFlags.mAugmentedAutofillOnly);
4128         }
4129         if (!mSessionFlags.mAugmentedAutofillOnly) return;
4130 
4131         forceRemoveFromServiceLocked();
4132     }
4133 
4134     @GuardedBy("mLock")
4135     void forceRemoveFromServiceLocked(int clientState) {
4136         if (sVerbose) Slog.v(TAG, "forceRemoveFromServiceLocked(): " + mPendingSaveUi);
4137 
4138         final boolean isPendingSaveUi = isSaveUiPendingLocked();
4139         mPendingSaveUi = null;
4140         removeFromServiceLocked();
4141         mUi.destroyAll(mPendingSaveUi, this, false);
4142         if (!isPendingSaveUi) {
4143             try {
4144                 mClient.setSessionFinished(clientState, /* autofillableIds= */ null);
4145             } catch (RemoteException e) {
4146                 Slog.e(TAG, "Error notifying client to finish session", e);
4147             }
4148         }
4149         destroyAugmentedAutofillWindowsLocked();
4150     }
4151 
4152     @GuardedBy("mLock")
4153     void destroyAugmentedAutofillWindowsLocked() {
4154         if (mAugmentedAutofillDestroyer != null) {
4155             mAugmentedAutofillDestroyer.run();
4156             mAugmentedAutofillDestroyer = null;
4157         }
4158     }
4159 
4160     /**
4161      * Thread-safe version of {@link #removeFromServiceLocked()}.
4162      */
4163     private void removeFromService() {
4164         synchronized (mLock) {
4165             removeFromServiceLocked();
4166         }
4167     }
4168 
4169     /**
4170      * Destroy this session and remove it from the service, but but only if it does not have a
4171      * pending Save UI.
4172      */
4173     @GuardedBy("mLock")
4174     void removeFromServiceLocked() {
4175         if (sVerbose) Slog.v(TAG, "removeFromServiceLocked(" + this.id + "): " + mPendingSaveUi);
4176         if (mDestroyed) {
4177             Slog.w(TAG, "Call to Session#removeFromServiceLocked() rejected - session: "
4178                     + id + " destroyed");
4179             return;
4180         }
4181         if (isSaveUiPendingLocked()) {
4182             Slog.i(TAG, "removeFromServiceLocked() ignored, waiting for pending save ui");
4183             return;
4184         }
4185 
4186         final RemoteFillService remoteFillService = destroyLocked();
4187         mService.removeSessionLocked(id);
4188         if (remoteFillService != null) {
4189             remoteFillService.destroy();
4190         }
4191         mSessionState = STATE_REMOVED;
4192     }
4193 
4194     void onPendingSaveUi(int operation, @NonNull IBinder token) {
4195         getUiForShowing().onPendingSaveUi(operation, token);
4196     }
4197 
4198     /**
4199      * Checks whether this session is hiding the Save UI to handle a custom description link for
4200      * a specific {@code token} created by
4201      * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
4202      */
4203     @GuardedBy("mLock")
4204     boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
4205         return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
4206     }
4207 
4208     /**
4209      * Checks whether this session is hiding the Save UI to handle a custom description link.
4210      */
4211     @GuardedBy("mLock")
4212     private boolean isSaveUiPendingLocked() {
4213         return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
4214     }
4215 
4216     @GuardedBy("mLock")
4217     private int getLastResponseIndexLocked() {
4218         // The response ids are monotonically increasing so
4219         // we just find the largest id which is the last. We
4220         // do not rely on the internal ordering in sparse
4221         // array to avoid - wow this stopped working!?
4222         int lastResponseIdx = -1;
4223         int lastResponseId = -1;
4224         if (mResponses != null) {
4225             final int responseCount = mResponses.size();
4226             for (int i = 0; i < responseCount; i++) {
4227                 if (mResponses.keyAt(i) > lastResponseId) {
4228                     lastResponseIdx = i;
4229                     lastResponseId = mResponses.keyAt(i);
4230                 }
4231             }
4232         }
4233         return lastResponseIdx;
4234     }
4235 
4236     private LogMaker newLogMaker(int category) {
4237         return newLogMaker(category, mService.getServicePackageName());
4238     }
4239 
4240     private LogMaker newLogMaker(int category, String servicePackageName) {
4241         return Helper.newLogMaker(category, mComponentName, servicePackageName, id, mCompatMode);
4242     }
4243 
4244     private void writeLog(int category) {
4245         mMetricsLogger.write(newLogMaker(category));
4246     }
4247 
4248     @GuardedBy("mLock")
4249     private void logAuthenticationStatusLocked(int requestId, int status) {
4250         addTaggedDataToRequestLogLocked(requestId,
4251                 MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status);
4252     }
4253 
4254     @GuardedBy("mLock")
4255     private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) {
4256         final LogMaker requestLog = mRequestLogs.get(requestId);
4257         if (requestLog == null) {
4258             Slog.w(TAG,
4259                     "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId);
4260             return;
4261         }
4262         requestLog.addTaggedData(tag, value);
4263     }
4264 
4265     @GuardedBy("mLock")
4266     private void logAugmentedAutofillRequestLocked(int mode,
4267             ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
4268             Boolean isInline) {
4269         final String historyItem =
4270                 "aug:id=" + id + " u=" + uid + " m=" + mode
4271                         + " a=" + ComponentName.flattenToShortString(mComponentName)
4272                         + " f=" + focusedId
4273                         + " s=" + augmentedRemoteServiceName
4274                         + " w=" + isWhitelisted
4275                         + " i=" + isInline;
4276         mService.getMaster().logRequestLocked(historyItem);
4277     }
4278 
4279     private void wtf(@Nullable Exception e, String fmt, Object...args) {
4280         final String message = String.format(fmt, args);
4281         synchronized (mLock) {
4282             mWtfHistory.log(message);
4283         }
4284 
4285         if (e != null) {
4286             Slog.wtf(TAG, message, e);
4287         } else {
4288             Slog.wtf(TAG, message);
4289         }
4290     }
4291 
4292     private static String actionAsString(int action) {
4293         switch (action) {
4294             case ACTION_START_SESSION:
4295                 return "START_SESSION";
4296             case ACTION_VIEW_ENTERED:
4297                 return "VIEW_ENTERED";
4298             case ACTION_VIEW_EXITED:
4299                 return "VIEW_EXITED";
4300             case ACTION_VALUE_CHANGED:
4301                 return "VALUE_CHANGED";
4302             case ACTION_RESPONSE_EXPIRED:
4303                 return "RESPONSE_EXPIRED";
4304             default:
4305                 return "UNKNOWN_" + action;
4306         }
4307     }
4308 
4309     private static String sessionStateAsString(@SessionState int sessionState) {
4310         switch (sessionState) {
4311             case STATE_UNKNOWN:
4312                 return "STATE_UNKNOWN";
4313             case STATE_ACTIVE:
4314                 return "STATE_ACTIVE";
4315             case STATE_FINISHED:
4316                 return "STATE_FINISHED";
4317             case STATE_REMOVED:
4318                 return "STATE_REMOVED";
4319             default:
4320                 return "UNKNOWN_SESSION_STATE_" + sessionState;
4321         }
4322     }
4323 }
4324