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