OSDN Git Service

Merge "Minor autofill logging improvements." into pi-dev
[android-x86/frameworks-base.git] / services / autofill / java / com / android / server / autofill / Session.java
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.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
20 import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
21 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
22 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
23 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
24 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
25 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
26 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
27 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
28
29 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
30 import static com.android.server.autofill.Helper.getNumericValue;
31 import static com.android.server.autofill.Helper.sDebug;
32 import static com.android.server.autofill.Helper.sPartitionMaxCount;
33 import static com.android.server.autofill.Helper.sVerbose;
34 import static com.android.server.autofill.Helper.toArray;
35 import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION;
36
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.app.Activity;
40 import android.app.ActivityManager;
41 import android.app.IAssistDataReceiver;
42 import android.app.assist.AssistStructure;
43 import android.app.assist.AssistStructure.AutofillOverlay;
44 import android.app.assist.AssistStructure.ViewNode;
45 import android.content.ComponentName;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.IntentSender;
49 import android.graphics.Bitmap;
50 import android.graphics.Rect;
51 import android.metrics.LogMaker;
52 import android.os.Binder;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.IBinder.DeathRecipient;
57 import android.os.Parcelable;
58 import android.os.RemoteCallback;
59 import android.os.RemoteException;
60 import android.os.SystemClock;
61 import android.service.autofill.AutofillFieldClassificationService.Scores;
62 import android.service.autofill.AutofillService;
63 import android.service.autofill.Dataset;
64 import android.service.autofill.FieldClassification;
65 import android.service.autofill.FieldClassification.Match;
66 import android.service.autofill.FillContext;
67 import android.service.autofill.FillRequest;
68 import android.service.autofill.FillResponse;
69 import android.service.autofill.InternalSanitizer;
70 import android.service.autofill.InternalValidator;
71 import android.service.autofill.SaveInfo;
72 import android.service.autofill.SaveRequest;
73 import android.service.autofill.UserData;
74 import android.service.autofill.ValueFinder;
75 import android.util.ArrayMap;
76 import android.util.ArraySet;
77 import android.util.LocalLog;
78 import android.util.Slog;
79 import android.util.SparseArray;
80 import android.util.TimeUtils;
81 import android.view.KeyEvent;
82 import android.view.autofill.AutofillId;
83 import android.view.autofill.AutofillManager;
84 import android.view.autofill.AutofillValue;
85 import android.view.autofill.IAutoFillManagerClient;
86 import android.view.autofill.IAutofillWindowPresenter;
87
88 import com.android.internal.R;
89 import com.android.internal.annotations.GuardedBy;
90 import com.android.internal.logging.MetricsLogger;
91 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
92 import com.android.internal.util.ArrayUtils;
93 import com.android.server.autofill.ui.AutoFillUI;
94 import com.android.server.autofill.ui.PendingUi;
95
96 import java.io.PrintWriter;
97 import java.io.StringWriter;
98 import java.util.ArrayList;
99 import java.util.Arrays;
100 import java.util.Collection;
101 import java.util.Collections;
102 import java.util.List;
103 import java.util.Objects;
104 import java.util.concurrent.atomic.AtomicInteger;
105
106 /**
107  * A session for a given activity.
108  *
109  * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
110  * of the current {@link ViewState} to display the appropriate UI.
111  *
112  * <p>Although the autofill requests and callbacks are stateless from the service's point of
113  * view, we need to keep state in the framework side for cases such as authentication. For
114  * example, when service return a {@link FillResponse} that contains all the fields needed
115  * to fill the activity but it requires authentication first, that response need to be held
116  * until the user authenticates or it times out.
117  */
118 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
119         AutoFillUI.AutoFillUiCallback, ValueFinder {
120     private static final String TAG = "AutofillSession";
121
122     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
123
124     private final AutofillManagerServiceImpl mService;
125     private final Handler mHandler;
126     private final Object mLock;
127     private final AutoFillUI mUi;
128
129     private final MetricsLogger mMetricsLogger = new MetricsLogger();
130
131     private static AtomicInteger sIdCounter = new AtomicInteger();
132
133     /** Id of the session */
134     public final int id;
135
136     /** uid the session is for */
137     public final int uid;
138
139     /** Flags used to start the session */
140     public final int mFlags;
141
142     @GuardedBy("mLock")
143     @NonNull private IBinder mActivityToken;
144
145     /** Component that's being auto-filled */
146     @NonNull private final ComponentName mComponentName;
147
148     /** Whether the app being autofilled is running in compat mode. */
149     private final boolean mCompatMode;
150
151     /** Node representing the URL bar on compat mode. */
152     @GuardedBy("mLock")
153     private ViewNode mUrlBar;
154
155     @GuardedBy("mLock")
156     private boolean mSaveOnAllViewsInvisible;
157
158     @GuardedBy("mLock")
159     private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
160
161     /**
162      * Id of the View currently being displayed.
163      */
164     @GuardedBy("mLock")
165     @Nullable private AutofillId mCurrentViewId;
166
167     @GuardedBy("mLock")
168     private IAutoFillManagerClient mClient;
169
170     @GuardedBy("mLock")
171     private DeathRecipient mClientVulture;
172
173     private final RemoteFillService mRemoteFillService;
174
175     @GuardedBy("mLock")
176     private SparseArray<FillResponse> mResponses;
177
178     /**
179      * Contexts read from the app; they will be updated (sanitized, change values for save) before
180      * sent to {@link AutofillService}. Ordered by the time they were read.
181      */
182     @GuardedBy("mLock")
183     private ArrayList<FillContext> mContexts;
184
185     /**
186      * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
187      */
188     private boolean mHasCallback;
189
190     /**
191      * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved
192      * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
193      */
194     @GuardedBy("mLock")
195     private Bundle mClientState;
196
197     @GuardedBy("mLock")
198     private boolean mDestroyed;
199
200     /** Whether the session is currently saving. */
201     @GuardedBy("mLock")
202     private boolean mIsSaving;
203
204     /**
205      * Helper used to handle state of Save UI when it must be hiding to show a custom description
206      * link and later recovered.
207      */
208     @GuardedBy("mLock")
209     private PendingUi mPendingSaveUi;
210
211     /**
212      * List of dataset ids selected by the user.
213      */
214     @GuardedBy("mLock")
215     private ArrayList<String> mSelectedDatasetIds;
216
217     /**
218      * When the session started (using elapsed time since boot).
219      */
220     private final long mStartTime;
221
222     /**
223      * When the UI was shown for the first time (using elapsed time since boot).
224      */
225     @GuardedBy("mLock")
226     private long mUiShownTime;
227
228     @GuardedBy("mLock")
229     private final LocalLog mUiLatencyHistory;
230
231     @GuardedBy("mLock")
232     private final LocalLog mWtfHistory;
233
234     /**
235      * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
236      */
237     @GuardedBy("mLock")
238     private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1);
239
240     /**
241      * Receiver of assist data from the app's {@link Activity}.
242      */
243     private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
244         @Override
245         public void onHandleAssistData(Bundle resultData) throws RemoteException {
246             final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
247             if (structure == null) {
248                 Slog.e(TAG, "No assist structure - app might have crashed providing it");
249                 return;
250             }
251
252             final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
253             if (receiverExtras == null) {
254                 Slog.e(TAG, "No receiver extras - app might have crashed providing it");
255                 return;
256             }
257
258             final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
259
260             if (sVerbose) {
261                 Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure);
262             }
263
264             final FillRequest request;
265             synchronized (mLock) {
266                 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(),
267                 // even if if the activity is gone by then, but structure .ensureData() gives a
268                 // ONE_WAY warning because system_service could block on app calls. We need to
269                 // change AssistStructure so it provides a "one-way" writeToParcel() method that
270                 // sends all the data
271                 try {
272                     structure.ensureDataForAutofill();
273                 } catch (RuntimeException e) {
274                     wtf(e, "Exception lazy loading assist structure for %s: %s",
275                             structure.getActivityComponent(), e);
276                     return;
277                 }
278
279                 // Sanitize structure before it's sent to service.
280                 final ComponentName componentNameFromApp = structure.getActivityComponent();
281                 if (componentNameFromApp == null || !mComponentName.getPackageName()
282                         .equals(componentNameFromApp.getPackageName())) {
283                     Slog.w(TAG, "Activity " + mComponentName + " forged different component on "
284                             + "AssistStructure: " + componentNameFromApp);
285                     structure.setActivityComponent(mComponentName);
286                     mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT)
287                             .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
288                                     componentNameFromApp == null ? "null"
289                                             : componentNameFromApp.flattenToShortString()));
290                 }
291                 if (mCompatMode) {
292                     // Sanitize URL bar, if needed
293                     final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode(
294                             mComponentName.getPackageName());
295                     if (sDebug) {
296                         Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds));
297                     }
298                     if (urlBarIds != null) {
299                         mUrlBar = Helper.sanitizeUrlBar(structure, urlBarIds);
300                         if (mUrlBar != null) {
301                             final AutofillId urlBarId = mUrlBar.getAutofillId();
302                             if (sDebug) {
303                                 Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain "
304                                         + mUrlBar.getWebDomain());
305                             }
306                             final ViewState viewState = new ViewState(Session.this, urlBarId,
307                                     Session.this, ViewState.STATE_URL_BAR);
308                             mViewStates.put(urlBarId, viewState);
309                         }
310                     }
311                 }
312                 structure.sanitizeForParceling(true);
313
314                 // Flags used to start the session.
315                 final int flags = structure.getFlags();
316
317                 if (mContexts == null) {
318                     mContexts = new ArrayList<>(1);
319                 }
320                 mContexts.add(new FillContext(requestId, structure));
321
322                 cancelCurrentRequestLocked();
323
324                 final int numContexts = mContexts.size();
325                 for (int i = 0; i < numContexts; i++) {
326                     fillContextWithAllowedValuesLocked(mContexts.get(i), flags);
327                 }
328
329                 // Dispatch a snapshot of the current contexts list since it may change
330                 // until the dispatch happens. The items in the list don't need to be cloned
331                 // since we don't hold on them anywhere else. The client state is not touched
332                 // by us, so no need to copy.
333                 request = new FillRequest(requestId, new ArrayList<>(mContexts),
334                         mClientState, flags);
335             }
336
337             mRemoteFillService.onFillRequest(request);
338         }
339
340         @Override
341         public void onHandleAssistScreenshot(Bitmap screenshot) {
342             // Do nothing
343         }
344     };
345
346     /**
347      * Returns the ids of all entries in {@link #mViewStates} in the same order.
348      */
349     @GuardedBy("mLock")
350     private AutofillId[] getIdsOfAllViewStatesLocked() {
351         final int numViewState = mViewStates.size();
352         final AutofillId[] ids = new AutofillId[numViewState];
353         for (int i = 0; i < numViewState; i++) {
354             ids[i] = mViewStates.valueAt(i).id;
355         }
356
357         return ids;
358     }
359
360     @Override
361     @Nullable
362     public String findByAutofillId(@NonNull AutofillId id) {
363         synchronized (mLock) {
364             AutofillValue value = findValueLocked(id);
365             if (value != null) {
366                 if (value.isText()) {
367                     return value.getTextValue().toString();
368                 }
369
370                 if (value.isList()) {
371                     final CharSequence[] options = getAutofillOptionsFromContextsLocked(id);
372                     if (options != null) {
373                         final int index = value.getListValue();
374                         final CharSequence option = options[index];
375                         return option != null ? option.toString() : null;
376                     } else {
377                         Slog.w(TAG, "findByAutofillId(): no autofill options for id " + id);
378                     }
379                 }
380             }
381         }
382         return null;
383     }
384
385     @Override
386     public AutofillValue findRawValueByAutofillId(AutofillId id) {
387         synchronized (mLock) {
388             return findValueLocked(id);
389         }
390     }
391
392     /**
393      * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
394      * or {@code null} when not found on either of them.
395      */
396     @GuardedBy("mLock")
397     private AutofillValue findValueLocked(@NonNull AutofillId id) {
398         final ViewState state = mViewStates.get(id);
399         if (state == null) {
400             if (sDebug) Slog.d(TAG, "findValueLocked(): no view state for " + id);
401             return null;
402         }
403         AutofillValue value = state.getCurrentValue();
404         if (value == null) {
405             if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + id);
406             value = getValueFromContextsLocked(id);
407         }
408         return value;
409     }
410
411     /**
412      * Updates values of the nodes in the context's structure so that:
413      *
414      * - proper node is focused
415      * - autofillValue is sent back to service when it was previously autofilled
416      * - autofillValue is sent in the view used to force a request
417      *
418      * @param fillContext The context to be filled
419      * @param flags The flags that started the session
420      */
421     @GuardedBy("mLock")
422     private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
423         final ViewNode[] nodes = fillContext
424                 .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
425
426         final int numViewState = mViewStates.size();
427         for (int i = 0; i < numViewState; i++) {
428             final ViewState viewState = mViewStates.valueAt(i);
429
430             final ViewNode node = nodes[i];
431             if (node == null) {
432                 if (sVerbose) {
433                     Slog.v(TAG,
434                             "fillContextWithAllowedValuesLocked(): no node for " + viewState.id);
435                 }
436                 continue;
437             }
438
439             final AutofillValue currentValue = viewState.getCurrentValue();
440             final AutofillValue filledValue = viewState.getAutofilledValue();
441             final AutofillOverlay overlay = new AutofillOverlay();
442
443             // Sanitizes the value if the current value matches what the service sent.
444             if (filledValue != null && filledValue.equals(currentValue)) {
445                 overlay.value = currentValue;
446             }
447
448             if (mCurrentViewId != null) {
449                 // Updates the focus value.
450                 overlay.focused = mCurrentViewId.equals(viewState.id);
451                 // Sanitizes the value of the focused field in a manual request.
452                 if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) {
453                     overlay.value = currentValue;
454                 }
455             }
456             node.setAutofillOverlay(overlay);
457         }
458     }
459
460     /**
461      * Cancels the last request sent to the {@link #mRemoteFillService}.
462      */
463     @GuardedBy("mLock")
464     private void cancelCurrentRequestLocked() {
465         final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
466
467         // Remove the FillContext as there will never be a response for the service
468         if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
469             final int numContexts = mContexts.size();
470
471             // It is most likely the last context, hence search backwards
472             for (int i = numContexts - 1; i >= 0; i--) {
473                 if (mContexts.get(i).getRequestId() == canceledRequest) {
474                     if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
475                     mContexts.remove(i);
476                     break;
477                 }
478             }
479         }
480     }
481
482     /**
483      * Reads a new structure and then request a new fill response from the fill service.
484      */
485     @GuardedBy("mLock")
486     private void requestNewFillResponseLocked(int flags) {
487         int requestId;
488
489         do {
490             requestId = sIdCounter.getAndIncrement();
491         } while (requestId == INVALID_REQUEST_ID);
492
493         // Create a metrics log for the request
494         final int ordinal = mRequestLogs.size() + 1;
495         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST)
496                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal);
497         if (flags != 0) {
498             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags);
499         }
500         mRequestLogs.put(requestId, log);
501
502         if (sVerbose) {
503             Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId="
504                     + requestId + ", flags=" + flags);
505         }
506
507         // If the focus changes very quickly before the first request is returned each focus change
508         // triggers a new partition and we end up with many duplicate partitions. This is
509         // enhanced as the focus change can be much faster than the taking of the assist structure.
510         // Hence remove the currently queued request and replace it with the one queued after the
511         // structure is taken. This causes only one fill request per bust of focus changes.
512         cancelCurrentRequestLocked();
513
514         try {
515             final Bundle receiverExtras = new Bundle();
516             receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
517             final long identity = Binder.clearCallingIdentity();
518             try {
519                 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver,
520                         receiverExtras, mActivityToken, flags)) {
521                     Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
522                 }
523             } finally {
524                 Binder.restoreCallingIdentity(identity);
525             }
526         } catch (RemoteException e) {
527             // Should not happen, it's a local call.
528         }
529     }
530
531     Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
532             @NonNull Context context, @NonNull Handler handler, int userId,
533             @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
534             @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
535             @NonNull LocalLog wtfHistory,
536             @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName,
537             boolean compatMode, boolean bindInstantServiceAllowed, int flags) {
538         id = sessionId;
539         mFlags = flags;
540         this.uid = uid;
541         mStartTime = SystemClock.elapsedRealtime();
542         mService = service;
543         mLock = lock;
544         mUi = ui;
545         mHandler = handler;
546         mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this,
547                 bindInstantServiceAllowed);
548         mActivityToken = activityToken;
549         mHasCallback = hasCallback;
550         mUiLatencyHistory = uiLatencyHistory;
551         mWtfHistory = wtfHistory;
552         mComponentName = componentName;
553         mCompatMode = compatMode;
554         setClientLocked(client);
555
556         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
557                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
558     }
559
560     /**
561      * Gets the currently registered activity token
562      *
563      * @return The activity token
564      */
565     @GuardedBy("mLock")
566     @NonNull IBinder getActivityTokenLocked() {
567         return mActivityToken;
568     }
569
570     /**
571      * Sets new activity and client for this session.
572      *
573      * @param newActivity The token of the new activity
574      * @param newClient The client receiving autofill callbacks
575      */
576     void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) {
577         synchronized (mLock) {
578             if (mDestroyed) {
579                 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: "
580                         + id + " destroyed");
581                 return;
582             }
583             mActivityToken = newActivity;
584             setClientLocked(newClient);
585
586             // The tracked id are not persisted in the client, hence update them
587             updateTrackedIdsLocked();
588         }
589     }
590
591     @GuardedBy("mLock")
592     private void setClientLocked(@NonNull IBinder client) {
593         unlinkClientVultureLocked();
594         mClient = IAutoFillManagerClient.Stub.asInterface(client);
595         mClientVulture = () -> {
596             Slog.d(TAG, "handling death of " + mActivityToken + " when saving=" + mIsSaving);
597             synchronized (mLock) {
598                 if (mIsSaving) {
599                     mUi.hideFillUi(this);
600                 } else {
601                     mUi.destroyAll(mPendingSaveUi, this, false);
602                 }
603             }
604         };
605         try {
606             mClient.asBinder().linkToDeath(mClientVulture, 0);
607         } catch (RemoteException e) {
608             Slog.w(TAG, "could not set binder death listener on autofill client: " + e);
609         }
610     }
611
612     @GuardedBy("mLock")
613     private void unlinkClientVultureLocked() {
614         if (mClient != null && mClientVulture != null) {
615             final boolean unlinked = mClient.asBinder().unlinkToDeath(mClientVulture, 0);
616             if (!unlinked) {
617                 Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken);
618             }
619         }
620     }
621
622     // FillServiceCallbacks
623     @Override
624     public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
625             @NonNull String servicePackageName, int requestFlags) {
626         final AutofillId[] fieldClassificationIds;
627
628         final LogMaker requestLog;
629
630         synchronized (mLock) {
631             if (mDestroyed) {
632                 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
633                         + id + " destroyed");
634                 return;
635             }
636
637             requestLog = mRequestLogs.get(requestId);
638             if (requestLog != null) {
639                 requestLog.setType(MetricsEvent.TYPE_SUCCESS);
640             } else {
641                 Slog.w(TAG, "onFillRequestSuccess(): no request log for id " + requestId);
642             }
643             if (response == null) {
644                 if (requestLog != null) {
645                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
646                 }
647                 processNullResponseLocked(requestFlags);
648                 return;
649             }
650
651             fieldClassificationIds = response.getFieldClassificationIds();
652             if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
653                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
654                 processNullResponseLocked(requestFlags);
655                 return;
656             }
657         }
658
659         mService.setLastResponse(id, response);
660
661         int sessionFinishedState = 0;
662         final long disableDuration = response.getDisableDuration();
663         if (disableDuration > 0) {
664             final int flags = response.getFlags();
665             if (sDebug) {
666                 final StringBuilder message = new StringBuilder("Service disabled autofill for ")
667                         .append(mComponentName)
668                         .append(": flags=").append(flags)
669                         .append(", duration=");
670                 TimeUtils.formatDuration(disableDuration, message);
671                 Slog.d(TAG, message.toString());
672             }
673             if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
674                 mService.disableAutofillForActivity(mComponentName, disableDuration,
675                         id, mCompatMode);
676             } else {
677                 mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
678                         id, mCompatMode);
679             }
680             sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
681         }
682
683         if (((response.getDatasets() == null || response.getDatasets().isEmpty())
684                         && response.getAuthentication() == null)
685                 || disableDuration > 0) {
686             // Response is "empty" from an UI point of view, need to notify client.
687             notifyUnavailableToClient(sessionFinishedState);
688         }
689
690         if (requestLog != null) {
691             requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
692                             response.getDatasets() == null ? 0 : response.getDatasets().size());
693             if (fieldClassificationIds != null) {
694                 requestLog.addTaggedData(
695                         MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS,
696                         fieldClassificationIds.length);
697             }
698         }
699
700         synchronized (mLock) {
701             processResponseLocked(response, null, requestFlags);
702         }
703     }
704
705     // FillServiceCallbacks
706     @Override
707     public void onFillRequestFailure(int requestId, @Nullable CharSequence message,
708             @NonNull String servicePackageName) {
709         onFillRequestFailureOrTimeout(requestId, false, message, servicePackageName);
710     }
711
712     // FillServiceCallbacks
713     @Override
714     public void onFillRequestTimeout(int requestId, @NonNull String servicePackageName) {
715         onFillRequestFailureOrTimeout(requestId, true, null, servicePackageName);
716     }
717
718     private void onFillRequestFailureOrTimeout(int requestId, boolean timedOut,
719             @Nullable CharSequence message, @NonNull String servicePackageName) {
720         synchronized (mLock) {
721             if (mDestroyed) {
722                 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
723                         + ") rejected - session: " + id + " destroyed");
724                 return;
725             }
726             mService.resetLastResponse();
727             final LogMaker requestLog = mRequestLogs.get(requestId);
728             if (requestLog == null) {
729                 Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId);
730             } else {
731                 requestLog.setType(timedOut ? MetricsEvent.TYPE_CLOSE : MetricsEvent.TYPE_FAILURE);
732             }
733         }
734         if (message != null) {
735             getUiForShowing().showError(message, this);
736         }
737         removeSelf();
738     }
739
740     // FillServiceCallbacks
741     @Override
742     public void onSaveRequestSuccess(@NonNull String servicePackageName,
743             @Nullable IntentSender intentSender) {
744         synchronized (mLock) {
745             mIsSaving = false;
746
747             if (mDestroyed) {
748                 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
749                         + id + " destroyed");
750                 return;
751             }
752         }
753         LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
754                 .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
755         mMetricsLogger.write(log);
756         if (intentSender != null) {
757             if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
758             startIntentSender(intentSender);
759         }
760
761         // Nothing left to do...
762         removeSelf();
763     }
764
765     // FillServiceCallbacks
766     @Override
767     public void onSaveRequestFailure(@Nullable CharSequence message,
768             @NonNull String servicePackageName) {
769         synchronized (mLock) {
770             mIsSaving = false;
771
772             if (mDestroyed) {
773                 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
774                         + id + " destroyed");
775                 return;
776             }
777         }
778         LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
779                 .setType(MetricsEvent.TYPE_FAILURE);
780         mMetricsLogger.write(log);
781
782         getUiForShowing().showError(message, this);
783         removeSelf();
784     }
785
786     /**
787      * Gets the {@link FillContext} for a request.
788      *
789      * @param requestId The id of the request
790      *
791      * @return The context or {@code null} if there is no context
792      */
793     @GuardedBy("mLock")
794     @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
795         if (mContexts == null) {
796             return null;
797         }
798
799         int numContexts = mContexts.size();
800         for (int i = 0; i < numContexts; i++) {
801             FillContext context = mContexts.get(i);
802
803             if (context.getRequestId() == requestId) {
804                 return context;
805             }
806         }
807
808         return null;
809     }
810
811     // FillServiceCallbacks
812     @Override
813     public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
814         if (sDebug) {
815             Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
816                     + "; intentSender=" + intent);
817         }
818         final Intent fillInIntent;
819         synchronized (mLock) {
820             if (mDestroyed) {
821                 Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
822                         + id + " destroyed");
823                 return;
824             }
825             fillInIntent = createAuthFillInIntentLocked(requestId, extras);
826             if (fillInIntent == null) {
827                 forceRemoveSelfLocked();
828                 return;
829             }
830         }
831
832         mService.setAuthenticationSelected(id, mClientState);
833
834         final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
835         mHandler.sendMessage(obtainMessage(
836                 Session::startAuthentication,
837                 this, authenticationId, intent, fillInIntent));
838     }
839
840     // FillServiceCallbacks
841     @Override
842     public void onServiceDied(RemoteFillService service) {
843         // TODO(b/337565347): implement
844     }
845
846     // AutoFillUiCallback
847     @Override
848     public void fill(int requestId, int datasetIndex, Dataset dataset) {
849         synchronized (mLock) {
850             if (mDestroyed) {
851                 Slog.w(TAG, "Call to Session#fill() rejected - session: "
852                         + id + " destroyed");
853                 return;
854             }
855         }
856         mHandler.sendMessage(obtainMessage(
857                 Session::autoFill,
858                 this, requestId, datasetIndex, dataset, true));
859     }
860
861     // AutoFillUiCallback
862     @Override
863     public void save() {
864         synchronized (mLock) {
865             if (mDestroyed) {
866                 Slog.w(TAG, "Call to Session#save() rejected - session: "
867                         + id + " destroyed");
868                 return;
869             }
870         }
871         mHandler.sendMessage(obtainMessage(
872                 AutofillManagerServiceImpl::handleSessionSave,
873                 mService, this));
874     }
875
876     // AutoFillUiCallback
877     @Override
878     public void cancelSave() {
879         synchronized (mLock) {
880             mIsSaving = false;
881
882             if (mDestroyed) {
883                 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
884                         + id + " destroyed");
885                 return;
886             }
887         }
888         mHandler.sendMessage(obtainMessage(
889                 Session::removeSelf, this));
890     }
891
892     // AutoFillUiCallback
893     @Override
894     public void requestShowFillUi(AutofillId id, int width, int height,
895             IAutofillWindowPresenter presenter) {
896         synchronized (mLock) {
897             if (mDestroyed) {
898                 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: "
899                         + id + " destroyed");
900                 return;
901             }
902             if (id.equals(mCurrentViewId)) {
903                 try {
904                     final ViewState view = mViewStates.get(id);
905                     mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(),
906                             presenter);
907                 } catch (RemoteException e) {
908                     Slog.e(TAG, "Error requesting to show fill UI", e);
909                 }
910             } else {
911                 if (sDebug) {
912                     Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view ("
913                             + mCurrentViewId + ") anymore");
914                 }
915             }
916         }
917     }
918
919     @Override
920     public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) {
921         synchronized (mLock) {
922             if (mDestroyed) {
923                 Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: "
924                         + id + " destroyed");
925                 return;
926             }
927             if (id.equals(mCurrentViewId)) {
928                 try {
929                     mClient.dispatchUnhandledKey(this.id, id, keyEvent);
930                 } catch (RemoteException e) {
931                     Slog.e(TAG, "Error requesting to dispatch unhandled key", e);
932                 }
933             } else {
934                 Slog.w(TAG, "Do not dispatch unhandled key on " + id
935                         + " as it is not the current view (" + mCurrentViewId + ") anymore");
936             }
937         }
938     }
939
940     // AutoFillUiCallback
941     @Override
942     public void requestHideFillUi(AutofillId id) {
943         synchronized (mLock) {
944             // NOTE: We allow this call in a destroyed state as the UI is
945             // asked to go away after we get destroyed, so let it do that.
946             try {
947                 mClient.requestHideFillUi(this.id, id);
948             } catch (RemoteException e) {
949                 Slog.e(TAG, "Error requesting to hide fill UI", e);
950             }
951         }
952     }
953
954     // AutoFillUiCallback
955     @Override
956     public void startIntentSender(IntentSender intentSender) {
957         synchronized (mLock) {
958             if (mDestroyed) {
959                 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: "
960                         + id + " destroyed");
961                 return;
962             }
963             removeSelfLocked();
964         }
965         mHandler.sendMessage(obtainMessage(
966                 Session::doStartIntentSender,
967                 this, intentSender));
968     }
969
970     private void doStartIntentSender(IntentSender intentSender) {
971         try {
972             synchronized (mLock) {
973                 mClient.startIntentSender(intentSender, null);
974             }
975         } catch (RemoteException e) {
976             Slog.e(TAG, "Error launching auth intent", e);
977         }
978     }
979
980     @GuardedBy("mLock")
981     void setAuthenticationResultLocked(Bundle data, int authenticationId) {
982         if (mDestroyed) {
983             Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
984                     + id + " destroyed");
985             return;
986         }
987         if (mResponses == null) {
988             // Typically happens when app explicitly called cancel() while the service was showing
989             // the auth UI.
990             Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
991             removeSelf();
992             return;
993         }
994         final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
995         final FillResponse authenticatedResponse = mResponses.get(requestId);
996         if (authenticatedResponse == null || data == null) {
997             removeSelf();
998             return;
999         }
1000
1001         final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(
1002                 authenticationId);
1003         // Authenticated a dataset - reset view state regardless if we got a response or a dataset
1004         if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
1005             final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx);
1006             if (dataset == null) {
1007                 removeSelf();
1008                 return;
1009             }
1010         }
1011
1012         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
1013         final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
1014         if (sDebug) {
1015             Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
1016                     + ", clientState=" + newClientState);
1017         }
1018         if (result instanceof FillResponse) {
1019             logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED);
1020             replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
1021         } else if (result instanceof Dataset) {
1022             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
1023                 logAuthenticationStatusLocked(requestId,
1024                         MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
1025                 if (newClientState != null) {
1026                     if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
1027                     mClientState = newClientState;
1028                 }
1029                 final Dataset dataset = (Dataset) result;
1030                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
1031                 autoFill(requestId, datasetIdx, dataset, false);
1032             } else {
1033                 logAuthenticationStatusLocked(requestId,
1034                         MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
1035             }
1036         } else {
1037             if (result != null) {
1038                 Slog.w(TAG, "service returned invalid auth type: " + result);
1039             }
1040             logAuthenticationStatusLocked(requestId,
1041                     MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
1042             processNullResponseLocked(0);
1043         }
1044     }
1045
1046     @GuardedBy("mLock")
1047     void setHasCallbackLocked(boolean hasIt) {
1048         if (mDestroyed) {
1049             Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
1050                     + id + " destroyed");
1051             return;
1052         }
1053         mHasCallback = hasIt;
1054     }
1055
1056     @GuardedBy("mLock")
1057     @Nullable
1058     private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
1059         if (mContexts == null) {
1060             if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
1061             return null;
1062         }
1063         if (mResponses == null) {
1064             // Happens when the activity / session was finished before the service replied, or
1065             // when the service cannot autofill it (and returned a null response).
1066             if (sVerbose && logPrefix != null) {
1067                 Slog.v(TAG, logPrefix + ": no responses on session");
1068             }
1069             return null;
1070         }
1071
1072         final int lastResponseIdx = getLastResponseIndexLocked();
1073         if (lastResponseIdx < 0) {
1074             if (logPrefix != null) {
1075                 Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
1076                         + ", mViewStates=" + mViewStates);
1077             }
1078             return null;
1079         }
1080
1081         final FillResponse response = mResponses.valueAt(lastResponseIdx);
1082         if (sVerbose && logPrefix != null) {
1083             Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
1084                     + ", mViewStates=" + mViewStates);
1085         }
1086         return response;
1087     }
1088
1089     @GuardedBy("mLock")
1090     @Nullable
1091     private SaveInfo getSaveInfoLocked() {
1092         final FillResponse response = getLastResponseLocked(null);
1093         return response == null ? null : response.getSaveInfo();
1094     }
1095
1096     /**
1097      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
1098      * when necessary.
1099      */
1100     public void logContextCommitted() {
1101         mHandler.sendMessage(obtainMessage(
1102                 Session::doLogContextCommitted, this));
1103     }
1104
1105     private void doLogContextCommitted() {
1106         synchronized (mLock) {
1107             logContextCommittedLocked();
1108         }
1109     }
1110
1111     @GuardedBy("mLock")
1112     private void logContextCommittedLocked() {
1113         final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
1114         if (lastResponse == null) return;
1115
1116         final int flags = lastResponse.getFlags();
1117         if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
1118             if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
1119             return;
1120         }
1121
1122         ArraySet<String> ignoredDatasets = null;
1123         ArrayList<AutofillId> changedFieldIds = null;
1124         ArrayList<String> changedDatasetIds = null;
1125         ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
1126
1127         boolean hasAtLeastOneDataset = false;
1128         final int responseCount = mResponses.size();
1129         for (int i = 0; i < responseCount; i++) {
1130             final FillResponse response = mResponses.valueAt(i);
1131             final List<Dataset> datasets = response.getDatasets();
1132             if (datasets == null || datasets.isEmpty()) {
1133                 if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
1134             } else {
1135                 for (int j = 0; j < datasets.size(); j++) {
1136                     final Dataset dataset = datasets.get(j);
1137                     final String datasetId = dataset.getId();
1138                     if (datasetId == null) {
1139                         if (sVerbose) {
1140                             Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
1141                         }
1142                     } else {
1143                         hasAtLeastOneDataset = true;
1144                         if (mSelectedDatasetIds == null
1145                                 || !mSelectedDatasetIds.contains(datasetId)) {
1146                             if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
1147                             if (ignoredDatasets == null) {
1148                                 ignoredDatasets = new ArraySet<>();
1149                             }
1150                             ignoredDatasets.add(datasetId);
1151                         }
1152                     }
1153                 }
1154             }
1155         }
1156         final AutofillId[] fieldClassificationIds = lastResponse.getFieldClassificationIds();
1157
1158         if (!hasAtLeastOneDataset && fieldClassificationIds == null) {
1159             if (sVerbose) {
1160                 Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
1161                         + "classification ids)");
1162             }
1163             return;
1164         }
1165
1166         final UserData userData = mService.getUserData();
1167
1168         for (int i = 0; i < mViewStates.size(); i++) {
1169             final ViewState viewState = mViewStates.valueAt(i);
1170             final int state = viewState.getState();
1171
1172             // When value changed, we need to log if it was:
1173             // - autofilled -> changedDatasetIds
1174             // - not autofilled but matches a dataset value -> manuallyFilledIds
1175             if ((state & ViewState.STATE_CHANGED) != 0) {
1176                 // Check if autofilled value was changed
1177                 if ((state & ViewState.STATE_AUTOFILLED) != 0) {
1178                     final String datasetId = viewState.getDatasetId();
1179                     if (datasetId == null) {
1180                         // Sanity check - should never happen.
1181                         Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
1182                         continue;
1183                     }
1184
1185                     // Must first check if final changed value is not the same as value sent by
1186                     // service.
1187                     final AutofillValue autofilledValue = viewState.getAutofilledValue();
1188                     final AutofillValue currentValue = viewState.getCurrentValue();
1189                     if (autofilledValue != null && autofilledValue.equals(currentValue)) {
1190                         if (sDebug) {
1191                             Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
1192                                     + " because it has same value that was autofilled");
1193                         }
1194                         continue;
1195                     }
1196
1197                     if (sDebug) {
1198                         Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
1199                     }
1200                     if (changedFieldIds == null) {
1201                         changedFieldIds = new ArrayList<>();
1202                         changedDatasetIds = new ArrayList<>();
1203                     }
1204                     changedFieldIds.add(viewState.id);
1205                     changedDatasetIds.add(datasetId);
1206                 } else {
1207                     final AutofillValue currentValue = viewState.getCurrentValue();
1208                     if (currentValue == null) {
1209                         if (sDebug) {
1210                             Slog.d(TAG, "logContextCommitted(): skipping view without current "
1211                                     + "value ( " + viewState + ")");
1212                         }
1213                         continue;
1214                     }
1215                     // Check if value match a dataset.
1216                     if (hasAtLeastOneDataset) {
1217                         for (int j = 0; j < responseCount; j++) {
1218                             final FillResponse response = mResponses.valueAt(j);
1219                             final List<Dataset> datasets = response.getDatasets();
1220                             if (datasets == null || datasets.isEmpty()) {
1221                                 if (sVerbose) {
1222                                     Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
1223                                 }
1224                             } else {
1225                                 for (int k = 0; k < datasets.size(); k++) {
1226                                     final Dataset dataset = datasets.get(k);
1227                                     final String datasetId = dataset.getId();
1228                                     if (datasetId == null) {
1229                                         if (sVerbose) {
1230                                             Slog.v(TAG, "logContextCommitted() skipping idless "
1231                                                     + "dataset " + dataset);
1232                                         }
1233                                     } else {
1234                                         final ArrayList<AutofillValue> values =
1235                                                 dataset.getFieldValues();
1236                                         for (int l = 0; l < values.size(); l++) {
1237                                             final AutofillValue candidate = values.get(l);
1238                                             if (currentValue.equals(candidate)) {
1239                                                 if (sDebug) {
1240                                                     Slog.d(TAG, "field " + viewState.id + " was "
1241                                                             + "manually filled with value set by "
1242                                                             + "dataset " + datasetId);
1243                                                 }
1244                                                 if (manuallyFilledIds == null) {
1245                                                     manuallyFilledIds = new ArrayMap<>();
1246                                                 }
1247                                                 ArraySet<String> datasetIds =
1248                                                         manuallyFilledIds.get(viewState.id);
1249                                                 if (datasetIds == null) {
1250                                                     datasetIds = new ArraySet<>(1);
1251                                                     manuallyFilledIds.put(viewState.id, datasetIds);
1252                                                 }
1253                                                 datasetIds.add(datasetId);
1254                                             }
1255                                         } // for l
1256                                         if (mSelectedDatasetIds == null
1257                                                 || !mSelectedDatasetIds.contains(datasetId)) {
1258                                             if (sVerbose) {
1259                                                 Slog.v(TAG, "adding ignored dataset " + datasetId);
1260                                             }
1261                                             if (ignoredDatasets == null) {
1262                                                 ignoredDatasets = new ArraySet<>();
1263                                             }
1264                                             ignoredDatasets.add(datasetId);
1265                                         } // if
1266                                     } // if
1267                                 } // for k
1268                             } // else
1269                         } // for j
1270                     }
1271
1272                 } // else
1273             } // else
1274         }
1275
1276         ArrayList<AutofillId> manuallyFilledFieldIds = null;
1277         ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
1278
1279         // Must "flatten" the map to the parcelable collection primitives
1280         if (manuallyFilledIds != null) {
1281             final int size = manuallyFilledIds.size();
1282             manuallyFilledFieldIds = new ArrayList<>(size);
1283             manuallyFilledDatasetIds = new ArrayList<>(size);
1284             for (int i = 0; i < size; i++) {
1285                 final AutofillId fieldId = manuallyFilledIds.keyAt(i);
1286                 final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
1287                 manuallyFilledFieldIds.add(fieldId);
1288                 manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
1289             }
1290         }
1291
1292         // Sets field classification scores
1293         final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
1294         if (userData != null && fcStrategy != null) {
1295             logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds,
1296                     changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
1297                     userData, mViewStates.values());
1298         } else {
1299             mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
1300                     ignoredDatasets, changedFieldIds, changedDatasetIds,
1301                     manuallyFilledFieldIds, manuallyFilledDatasetIds,
1302                     mComponentName, mCompatMode);
1303         }
1304     }
1305
1306     /**
1307      * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
1308      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
1309      */
1310     private void logFieldClassificationScoreLocked(
1311             @NonNull FieldClassificationStrategy fcStrategy,
1312             @NonNull ArraySet<String> ignoredDatasets,
1313             @NonNull ArrayList<AutofillId> changedFieldIds,
1314             @NonNull ArrayList<String> changedDatasetIds,
1315             @NonNull ArrayList<AutofillId> manuallyFilledFieldIds,
1316             @NonNull ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
1317             @NonNull UserData userData, @NonNull Collection<ViewState> viewStates) {
1318
1319         final String[] userValues = userData.getValues();
1320         final String[] categoryIds = userData.getCategoryIds();
1321
1322         // Sanity check
1323         if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) {
1324             final int valuesLength = userValues == null ? -1 : userValues.length;
1325             final int idsLength = categoryIds == null ? -1 : categoryIds.length;
1326             Slog.w(TAG, "setScores(): user data mismatch: values.length = "
1327                     + valuesLength + ", ids.length = " + idsLength);
1328             return;
1329         }
1330
1331         final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
1332
1333         final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize);
1334         final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
1335                 maxFieldsSize);
1336
1337         final String algorithm = userData.getFieldClassificationAlgorithm();
1338         final Bundle algorithmArgs = userData.getAlgorithmArgs();
1339         final int viewsSize = viewStates.size();
1340
1341         // First, we get all scores.
1342         final AutofillId[] autofillIds = new AutofillId[viewsSize];
1343         final ArrayList<AutofillValue> currentValues = new ArrayList<>(viewsSize);
1344         int k = 0;
1345         for (ViewState viewState : viewStates) {
1346             currentValues.add(viewState.getCurrentValue());
1347             autofillIds[k++] = viewState.id;
1348         }
1349
1350         // Then use the results, asynchronously
1351         final RemoteCallback callback = new RemoteCallback((result) -> {
1352             if (result == null) {
1353                 if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
1354                 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
1355                         ignoredDatasets, changedFieldIds, changedDatasetIds,
1356                         manuallyFilledFieldIds, manuallyFilledDatasetIds,
1357                         mComponentName, mCompatMode);
1358                 return;
1359             }
1360             final Scores scores = result.getParcelable(EXTRA_SCORES);
1361             if (scores == null) {
1362                 Slog.w(TAG, "No field classification score on " + result);
1363                 return;
1364             }
1365             int i = 0, j = 0;
1366             try {
1367                 // Iteract over all autofill fields first
1368                 for (i = 0; i < viewsSize; i++) {
1369                     final AutofillId autofillId = autofillIds[i];
1370
1371                     // Search the best scores for each category (as some categories could have
1372                     // multiple user values
1373                     ArrayMap<String, Float> scoresByField = null;
1374                     for (j = 0; j < userValues.length; j++) {
1375                         final String categoryId = categoryIds[j];
1376                         final float score = scores.scores[i][j];
1377                         if (score > 0) {
1378                             if (scoresByField == null) {
1379                                 scoresByField = new ArrayMap<>(userValues.length);
1380                             }
1381                             final Float currentScore = scoresByField.get(categoryId);
1382                             if (currentScore != null && currentScore > score) {
1383                                 if (sVerbose) {
1384                                     Slog.v(TAG,  "skipping score " + score
1385                                             + " because it's less than " + currentScore);
1386                                 }
1387                                 continue;
1388                             }
1389                             if (sVerbose) {
1390                                 Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
1391                                         + autofillId);
1392                             }
1393                             scoresByField.put(categoryId, score);
1394                         }
1395                         else if (sVerbose) {
1396                             Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
1397                         }
1398                     }
1399                     if (scoresByField == null) {
1400                         if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
1401                         continue;
1402                     }
1403
1404                     // Then create the matches for that autofill id
1405                     final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
1406                     for (j = 0; j < scoresByField.size(); j++) {
1407                         final String fieldId = scoresByField.keyAt(j);
1408                         final float score = scoresByField.valueAt(j);
1409                         matches.add(new Match(fieldId, score));
1410                     }
1411                     detectedFieldIds.add(autofillId);
1412                     detectedFieldClassifications.add(new FieldClassification(matches));
1413                 } // for i
1414             } catch (ArrayIndexOutOfBoundsException e) {
1415                 wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
1416                 return;
1417             }
1418
1419             mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
1420                     ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
1421                     manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
1422                     mComponentName, mCompatMode);
1423         });
1424
1425         fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
1426     }
1427
1428     /**
1429      * Shows the save UI, when session can be saved.
1430      *
1431      * @return {@code true} if session is done, or {@code false} if it's pending user action.
1432      */
1433     @GuardedBy("mLock")
1434     public boolean showSaveLocked() {
1435         if (mDestroyed) {
1436             Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
1437                     + id + " destroyed");
1438             return false;
1439         }
1440         final FillResponse response = getLastResponseLocked("showSaveLocked()");
1441         final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
1442
1443         /*
1444          * The Save dialog is only shown if all conditions below are met:
1445          *
1446          * - saveInfo is not null.
1447          * - autofillValue of all required ids is not null.
1448          * - autofillValue of at least one id (required or optional) has changed.
1449          * - there is no Dataset in the last FillResponse whose values of all dataset fields matches
1450          *   the current values of all fields in the screen.
1451          */
1452         if (saveInfo == null) {
1453             if (sVerbose) Slog.v(TAG, "showSaveLocked(): no saveInfo from service");
1454             return true;
1455         }
1456
1457         final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
1458
1459         // Cache used to make sure changed fields do not belong to a dataset.
1460         final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
1461         final ArraySet<AutofillId> allIds = new ArraySet<>();
1462
1463         final AutofillId[] requiredIds = saveInfo.getRequiredIds();
1464         boolean allRequiredAreNotEmpty = true;
1465         boolean atLeastOneChanged = false;
1466         if (requiredIds != null) {
1467             for (int i = 0; i < requiredIds.length; i++) {
1468                 final AutofillId id = requiredIds[i];
1469                 if (id == null) {
1470                     Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds));
1471                     continue;
1472                 }
1473                 allIds.add(id);
1474                 final ViewState viewState = mViewStates.get(id);
1475                 if (viewState == null) {
1476                     Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
1477                     allRequiredAreNotEmpty = false;
1478                     break;
1479                 }
1480
1481                 AutofillValue value = viewState.getCurrentValue();
1482                 if (value == null || value.isEmpty()) {
1483                     final AutofillValue initialValue = getValueFromContextsLocked(id);
1484                     if (initialValue != null) {
1485                         if (sDebug) {
1486                             Slog.d(TAG, "Value of required field " + id + " didn't change; "
1487                                     + "using initial value (" + initialValue + ") instead");
1488                         }
1489                         value = initialValue;
1490                     } else {
1491                         if (sDebug) {
1492                             Slog.d(TAG, "empty value for required " + id );
1493                         }
1494                         allRequiredAreNotEmpty = false;
1495                         break;
1496                     }
1497                 }
1498
1499                 value = getSanitizedValue(sanitizers, id, value);
1500                 if (value == null) {
1501                     if (sDebug) {
1502                         Slog.d(TAG, "value of required field " + id + " failed sanitization");
1503                     }
1504                     allRequiredAreNotEmpty = false;
1505                     break;
1506                 }
1507                 viewState.setSanitizedValue(value);
1508                 currentValues.put(id, value);
1509                 final AutofillValue filledValue = viewState.getAutofilledValue();
1510
1511                 if (!value.equals(filledValue)) {
1512                     boolean changed = true;
1513                     if (filledValue == null) {
1514                         // Dataset was not autofilled, make sure initial value didn't change.
1515                         final AutofillValue initialValue = getValueFromContextsLocked(id);
1516                         if (initialValue != null && initialValue.equals(value)) {
1517                             if (sDebug) {
1518                                 Slog.d(TAG, "id " + id + " is part of dataset but initial value "
1519                                         + "didn't change: " + value);
1520                             }
1521                             changed = false;
1522                         }
1523                     }
1524                     if (changed) {
1525                         if (sDebug) {
1526                             Slog.d(TAG, "found a change on required " + id + ": " + filledValue
1527                                     + " => " + value);
1528                         }
1529                         atLeastOneChanged = true;
1530                     }
1531                 }
1532             }
1533         }
1534
1535         final AutofillId[] optionalIds = saveInfo.getOptionalIds();
1536         if (allRequiredAreNotEmpty) {
1537             if (!atLeastOneChanged && optionalIds != null) {
1538                 // No change on required ids yet, look for changes on optional ids.
1539                 for (int i = 0; i < optionalIds.length; i++) {
1540                     final AutofillId id = optionalIds[i];
1541                     allIds.add(id);
1542                     final ViewState viewState = mViewStates.get(id);
1543                     if (viewState == null) {
1544                         Slog.w(TAG, "no ViewState for optional " + id);
1545                         continue;
1546                     }
1547                     if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
1548                         final AutofillValue currentValue = viewState.getCurrentValue();
1549                         currentValues.put(id, currentValue);
1550                         final AutofillValue filledValue = viewState.getAutofilledValue();
1551                         if (currentValue != null && !currentValue.equals(filledValue)) {
1552                             if (sDebug) {
1553                                 Slog.d(TAG, "found a change on optional " + id + ": " + filledValue
1554                                         + " => " + currentValue);
1555                             }
1556                             atLeastOneChanged = true;
1557                             break;
1558                         }
1559                     } else {
1560                         // Update current values cache based on initial value
1561                         final AutofillValue initialValue = getValueFromContextsLocked(id);
1562                         if (sDebug) {
1563                             Slog.d(TAG, "no current value for " + id + "; initial value is "
1564                                     + initialValue);
1565                         }
1566                         if (initialValue != null) {
1567                             currentValues.put(id, initialValue);
1568                         }
1569                     }
1570                 }
1571             }
1572             if (atLeastOneChanged) {
1573                 if (sDebug) {
1574                     Slog.d(TAG, "at least one field changed, validate fields for save UI");
1575                 }
1576                 final InternalValidator validator = saveInfo.getValidator();
1577                 if (validator != null) {
1578                     final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
1579                     boolean isValid;
1580                     try {
1581                         isValid = validator.isValid(this);
1582                         if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
1583                         log.setType(isValid
1584                                 ? MetricsEvent.TYPE_SUCCESS
1585                                 : MetricsEvent.TYPE_DISMISS);
1586                     } catch (Exception e) {
1587                         Slog.e(TAG, "Not showing save UI because validation failed:", e);
1588                         log.setType(MetricsEvent.TYPE_FAILURE);
1589                         mMetricsLogger.write(log);
1590                         return true;
1591                     }
1592
1593                     mMetricsLogger.write(log);
1594                     if (!isValid) {
1595                         Slog.i(TAG, "not showing save UI because fields failed validation");
1596                         return true;
1597                     }
1598                 }
1599
1600                 // Make sure the service doesn't have the fields already by checking the datasets
1601                 // content.
1602                 final List<Dataset> datasets = response.getDatasets();
1603                 if (datasets != null) {
1604                     datasets_loop: for (int i = 0; i < datasets.size(); i++) {
1605                         final Dataset dataset = datasets.get(i);
1606                         final ArrayMap<AutofillId, AutofillValue> datasetValues =
1607                                 Helper.getFields(dataset);
1608                         if (sVerbose) {
1609                             Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i
1610                                     + ": " + dataset + "; allIds=" + allIds);
1611                         }
1612                         for (int j = 0; j < allIds.size(); j++) {
1613                             final AutofillId id = allIds.valueAt(j);
1614                             final AutofillValue currentValue = currentValues.get(id);
1615                             if (currentValue == null) {
1616                                 if (sDebug) {
1617                                     Slog.d(TAG, "dataset has value for field that is null: " + id);
1618                                 }
1619                                 continue datasets_loop;
1620                             }
1621                             final AutofillValue datasetValue = datasetValues.get(id);
1622                             if (!currentValue.equals(datasetValue)) {
1623                                 if (sDebug) {
1624                                     Slog.d(TAG, "found a dataset change on id " + id + ": from "
1625                                             + datasetValue + " to " + currentValue);
1626                                 }
1627                                 continue datasets_loop;
1628                             }
1629                             if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id);
1630                         }
1631                         if (sDebug) {
1632                             Slog.d(TAG, "ignoring Save UI because all fields match contents of "
1633                                     + "dataset #" + i + ": " + dataset);
1634                         }
1635                         return true;
1636                     }
1637                 }
1638
1639                 if (sDebug) {
1640                     Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
1641                             + id + "!");
1642                 }
1643
1644                 // Use handler so logContextCommitted() is logged first
1645                 mHandler.sendMessage(obtainMessage(
1646                         Session::logSaveShown, this));
1647
1648                 final IAutoFillManagerClient client = getClient();
1649                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
1650                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
1651                         mService.getServicePackageName(), saveInfo, this,
1652                         mComponentName, this, mPendingSaveUi, mCompatMode);
1653                 if (client != null) {
1654                     try {
1655                         client.setSaveUiState(id, true);
1656                     } catch (RemoteException e) {
1657                         Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e);
1658                     }
1659                 }
1660                 mIsSaving = true;
1661                 return false;
1662             }
1663         }
1664         // Nothing changed...
1665         if (sDebug) {
1666             Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities."
1667                     + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
1668                     + ", atLeastOneChanged=" + atLeastOneChanged);
1669         }
1670         return true;
1671     }
1672
1673     private void logSaveShown() {
1674         mService.logSaveShown(id, mClientState);
1675     }
1676
1677     @Nullable
1678     private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
1679         if (saveInfo == null) return null;
1680
1681         final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
1682         if (sanitizerKeys == null) return null;
1683
1684         final int size = sanitizerKeys.length ;
1685         final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
1686         if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
1687         final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
1688         for (int i = 0; i < size; i++) {
1689             final InternalSanitizer sanitizer = sanitizerKeys[i];
1690             final AutofillId[] ids = sanitizerValues[i];
1691             if (sDebug) {
1692                 Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
1693                         + Arrays.toString(ids));
1694             }
1695             for (AutofillId id : ids) {
1696                 sanitizers.put(id, sanitizer);
1697             }
1698         }
1699         return sanitizers;
1700     }
1701
1702     @Nullable
1703     private AutofillValue getSanitizedValue(
1704             @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
1705             @NonNull AutofillId id,
1706             @NonNull AutofillValue value) {
1707         if (sanitizers == null) return value;
1708
1709         final InternalSanitizer sanitizer = sanitizers.get(id);
1710         if (sanitizer == null) {
1711             return value;
1712         }
1713
1714         final AutofillValue sanitized = sanitizer.sanitize(value);
1715         if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
1716         return sanitized;
1717     }
1718
1719     /**
1720      * Returns whether the session is currently showing the save UI
1721      */
1722     @GuardedBy("mLock")
1723     boolean isSavingLocked() {
1724         return mIsSaving;
1725     }
1726
1727     /**
1728      * Gets the latest non-empty value for the given id in the autofill contexts.
1729      */
1730     @GuardedBy("mLock")
1731     @Nullable
1732     private AutofillValue getValueFromContextsLocked(AutofillId id) {
1733         final int numContexts = mContexts.size();
1734         for (int i = numContexts - 1; i >= 0; i--) {
1735             final FillContext context = mContexts.get(i);
1736             final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), id);
1737             if (node != null) {
1738                 final AutofillValue value = node.getAutofillValue();
1739                 if (sDebug) {
1740                     Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + value);
1741                 }
1742                 if (value != null && !value.isEmpty()) {
1743                     return value;
1744                 }
1745             }
1746         }
1747         return null;
1748     }
1749
1750     /**
1751      * Gets the latest autofill options for the given id in the autofill contexts.
1752      */
1753     @GuardedBy("mLock")
1754     @Nullable
1755     private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
1756         final int numContexts = mContexts.size();
1757
1758         for (int i = numContexts - 1; i >= 0; i--) {
1759             final FillContext context = mContexts.get(i);
1760             final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), id);
1761             if (node != null && node.getAutofillOptions() != null) {
1762                 return node.getAutofillOptions();
1763             }
1764         }
1765         return null;
1766     }
1767
1768     /**
1769      * Calls service when user requested save.
1770      */
1771     @GuardedBy("mLock")
1772     void callSaveLocked() {
1773         if (mDestroyed) {
1774             Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
1775                     + id + " destroyed");
1776             return;
1777         }
1778
1779         if (sVerbose) Slog.v(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
1780
1781         if (mContexts == null) {
1782             Slog.w(TAG, "callSaveLocked(): no contexts");
1783             return;
1784         }
1785
1786         final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
1787                 createSanitizers(getSaveInfoLocked());
1788
1789         final int numContexts = mContexts.size();
1790
1791         for (int contextNum = 0; contextNum < numContexts; contextNum++) {
1792             final FillContext context = mContexts.get(contextNum);
1793
1794             final ViewNode[] nodes =
1795                 context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
1796
1797             if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
1798
1799             for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
1800                 final ViewState viewState = mViewStates.valueAt(viewStateNum);
1801
1802                 final AutofillId id = viewState.id;
1803                 final AutofillValue value = viewState.getCurrentValue();
1804                 if (value == null) {
1805                     if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
1806                     continue;
1807                 }
1808                 final ViewNode node = nodes[viewStateNum];
1809                 if (node == null) {
1810                     Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
1811                     continue;
1812                 }
1813                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
1814
1815                 AutofillValue sanitizedValue = viewState.getSanitizedValue();
1816
1817                 if (sanitizedValue == null) {
1818                     // Field is optional and haven't been sanitized yet.
1819                     sanitizedValue = getSanitizedValue(sanitizers, id, value);
1820                 }
1821                 if (sanitizedValue != null) {
1822                     node.updateAutofillValue(sanitizedValue);
1823                 } else if (sDebug) {
1824                     Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
1825                 }
1826             }
1827
1828             // Sanitize structure before it's sent to service.
1829             context.getStructure().sanitizeForParceling(false);
1830
1831             if (sVerbose) {
1832                 Slog.v(TAG, "Dumping structure of " + context + " before calling service.save()");
1833                 context.getStructure().dump(false);
1834             }
1835         }
1836
1837         // Remove pending fill requests as the session is finished.
1838         cancelCurrentRequestLocked();
1839
1840         // Dispatch a snapshot of the current contexts list since it may change
1841         // until the dispatch happens. The items in the list don't need to be cloned
1842         // since we don't hold on them anywhere else. The client state is not touched
1843         // by us, so no need to copy.
1844         final SaveRequest saveRequest = new SaveRequest(new ArrayList<>(mContexts), mClientState,
1845                 mSelectedDatasetIds);
1846         mRemoteFillService.onSaveRequest(saveRequest);
1847     }
1848
1849     /**
1850      * Starts (if necessary) a new fill request upon entering a view.
1851      *
1852      * <p>A new request will be started in 2 scenarios:
1853      * <ol>
1854      *   <li>If the user manually requested autofill.
1855      *   <li>If the view is part of a new partition.
1856      * </ol>
1857      *
1858      * @param id The id of the view that is entered.
1859      * @param viewState The view that is entered.
1860      * @param flags The flag that was passed by the AutofillManager.
1861      */
1862     @GuardedBy("mLock")
1863     private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
1864             @NonNull ViewState viewState, int flags) {
1865         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
1866             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
1867             viewState.setState(STATE_RESTARTED_SESSION);
1868             requestNewFillResponseLocked(flags);
1869             return;
1870         }
1871
1872         // If it's not, then check if it it should start a partition.
1873         if (shouldStartNewPartitionLocked(id)) {
1874             if (sDebug) {
1875                 Slog.d(TAG, "Starting partition for view id " + id + ": "
1876                         + viewState.getStateAsString());
1877             }
1878             viewState.setState(ViewState.STATE_STARTED_PARTITION);
1879             requestNewFillResponseLocked(flags);
1880         } else {
1881             if (sVerbose) {
1882                 Slog.v(TAG, "Not starting new partition for view " + id + ": "
1883                         + viewState.getStateAsString());
1884             }
1885         }
1886     }
1887
1888     /**
1889      * Determines if a new partition should be started for an id.
1890      *
1891      * @param id The id of the view that is entered
1892      *
1893      * @return {@code true} iff a new partition should be started
1894      */
1895     @GuardedBy("mLock")
1896     private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
1897         if (mResponses == null) {
1898             return true;
1899         }
1900
1901         final int numResponses = mResponses.size();
1902         if (numResponses >= sPartitionMaxCount) {
1903             Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
1904                     + " reached maximum of " + sPartitionMaxCount);
1905             return false;
1906         }
1907
1908         for (int responseNum = 0; responseNum < numResponses; responseNum++) {
1909             final FillResponse response = mResponses.valueAt(responseNum);
1910
1911             if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
1912                 return false;
1913             }
1914
1915             final SaveInfo saveInfo = response.getSaveInfo();
1916             if (saveInfo != null) {
1917                 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id)
1918                         || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) {
1919                     return false;
1920                 }
1921             }
1922
1923             final List<Dataset> datasets = response.getDatasets();
1924             if (datasets != null) {
1925                 final int numDatasets = datasets.size();
1926
1927                 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) {
1928                     final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds();
1929
1930                     if (fields != null && fields.contains(id)) {
1931                         return false;
1932                     }
1933                 }
1934             }
1935
1936             if (ArrayUtils.contains(response.getAuthenticationIds(), id)) {
1937                 return false;
1938             }
1939         }
1940
1941         return true;
1942     }
1943
1944     @GuardedBy("mLock")
1945     void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
1946             int flags) {
1947         if (mDestroyed) {
1948             Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
1949                     + id + " destroyed");
1950             return;
1951         }
1952         if (sVerbose) {
1953             Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + actionAsString(action)
1954                     + ", flags=" + flags);
1955         }
1956         ViewState viewState = mViewStates.get(id);
1957
1958         if (viewState == null) {
1959             if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED
1960                     || action == ACTION_VIEW_ENTERED) {
1961                 if (sVerbose) Slog.v(TAG, "Creating viewState for " + id);
1962                 boolean isIgnored = isIgnoredLocked(id);
1963                 viewState = new ViewState(this, id, this,
1964                         isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
1965                 mViewStates.put(id, viewState);
1966
1967                 // TODO(b/73648631): for optimization purposes, should also ignore if change is
1968                 // detectable, and batch-send them when the session is finished (but that will
1969                 // require tracking detectable fields on AutofillManager)
1970                 if (isIgnored) {
1971                     if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + viewState);
1972                     return;
1973                 }
1974             } else {
1975                 if (sVerbose) Slog.v(TAG, "Ignoring specific action when viewState=null");
1976                 return;
1977             }
1978         }
1979
1980         switch(action) {
1981             case ACTION_START_SESSION:
1982                 // View is triggering autofill.
1983                 mCurrentViewId = viewState.id;
1984                 viewState.update(value, virtualBounds, flags);
1985                 viewState.setState(ViewState.STATE_STARTED_SESSION);
1986                 requestNewFillResponseLocked(flags);
1987                 break;
1988             case ACTION_VALUE_CHANGED:
1989                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
1990                     // Must cancel the session if the value of the URL bar changed
1991                     final String currentUrl = mUrlBar == null ? null
1992                             : mUrlBar.getText().toString().trim();
1993                     if (currentUrl == null) {
1994                         // Sanity check - shouldn't happen.
1995                         wtf(null, "URL bar value changed, but current value is null");
1996                         return;
1997                     }
1998                     if (value == null || ! value.isText()) {
1999                         // Sanity check - shouldn't happen.
2000                         wtf(null, "URL bar value changed to null or non-text: %s", value);
2001                         return;
2002                     }
2003                     final String newUrl = value.getTextValue().toString();
2004                     if (newUrl.equals(currentUrl)) {
2005                         if (sDebug) Slog.d(TAG, "Ignoring change on URL bar as it's the same");
2006                         return;
2007                     }
2008                     if (mSaveOnAllViewsInvisible) {
2009                         // We cannot cancel the session because it could hinder Save when all views
2010                         // are finished, as the URL bar changed callback is usually called before
2011                         // the virtual views become invisible.
2012                         if (sDebug) {
2013                             Slog.d(TAG, "Ignoring change on URL because session will finish when "
2014                                     + "views are gone");
2015                         }
2016                         return;
2017                     }
2018                     if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed");
2019                     forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE);
2020                     return;
2021                 }
2022
2023                 if (!Objects.equals(value, viewState.getCurrentValue())) {
2024                     if ((value == null || value.isEmpty())
2025                             && viewState.getCurrentValue() != null
2026                             && viewState.getCurrentValue().isText()
2027                             && viewState.getCurrentValue().getTextValue() != null
2028                             && getSaveInfoLocked() != null) {
2029                         final int length = viewState.getCurrentValue().getTextValue().length();
2030                         if (sDebug) {
2031                             Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
2032                                     + length + " chars long");
2033                         }
2034                         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
2035                                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
2036                         mMetricsLogger.write(log);
2037                     }
2038
2039                     // Always update the internal state.
2040                     viewState.setCurrentValue(value);
2041
2042                     // Must check if this update was caused by autofilling the view, in which
2043                     // case we just update the value, but not the UI.
2044                     final AutofillValue filledValue = viewState.getAutofilledValue();
2045                     if (filledValue != null && filledValue.equals(value)) {
2046                         if (sVerbose) {
2047                             Slog.v(TAG, "ignoring autofilled change on id " + id);
2048                         }
2049                         return;
2050                     }
2051                     // Update the internal state...
2052                     viewState.setState(ViewState.STATE_CHANGED);
2053
2054                     //..and the UI
2055                     final String filterText;
2056                     if (value == null || !value.isText()) {
2057                         filterText = null;
2058                     } else {
2059                         final CharSequence text = value.getTextValue();
2060                         // Text should never be null, but it doesn't hurt to check to avoid a
2061                         // system crash...
2062                         filterText = (text == null) ? null : text.toString();
2063                     }
2064                     getUiForShowing().filterFillUi(filterText, this);
2065                 }
2066                 break;
2067             case ACTION_VIEW_ENTERED:
2068                 if (sVerbose && virtualBounds != null) {
2069                     Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
2070                 }
2071
2072                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
2073                     if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
2074                     return;
2075                 }
2076
2077                 requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
2078
2079                 // Remove the UI if the ViewState has changed.
2080                 if (mCurrentViewId != viewState.id) {
2081                     mUi.hideFillUi(this);
2082                     mCurrentViewId = viewState.id;
2083                 }
2084
2085                 // If the ViewState is ready to be displayed, onReady() will be called.
2086                 viewState.update(value, virtualBounds, flags);
2087                 break;
2088             case ACTION_VIEW_EXITED:
2089                 if (mCurrentViewId == viewState.id) {
2090                     if (sVerbose) Slog.d(TAG, "Exiting view " + id);
2091                     mUi.hideFillUi(this);
2092                     mCurrentViewId = null;
2093                 }
2094                 break;
2095             default:
2096                 Slog.w(TAG, "updateLocked(): unknown action: " + action);
2097         }
2098     }
2099
2100     /**
2101      * Checks whether a view should be ignored.
2102      */
2103     @GuardedBy("mLock")
2104     private boolean isIgnoredLocked(AutofillId id) {
2105         // Always check the latest response only
2106         final FillResponse response = getLastResponseLocked(null);
2107         if (response == null) return false;
2108
2109         return ArrayUtils.contains(response.getIgnoredIds(), id);
2110     }
2111
2112     @Override
2113     public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
2114             @Nullable AutofillValue value) {
2115         synchronized (mLock) {
2116             if (mDestroyed) {
2117                 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
2118                         + id + " destroyed");
2119                 return;
2120             }
2121         }
2122
2123         String filterText = null;
2124         if (value != null && value.isText()) {
2125             filterText = value.getTextValue().toString();
2126         }
2127
2128         getUiForShowing().showFillUi(filledId, response, filterText,
2129                 mService.getServicePackageName(), mComponentName,
2130                 mService.getServiceLabel(), mService.getServiceIcon(), this, id, mCompatMode);
2131
2132         synchronized (mLock) {
2133             if (mUiShownTime == 0) {
2134                 // Log first time UI is shown.
2135                 mUiShownTime = SystemClock.elapsedRealtime();
2136                 final long duration = mUiShownTime - mStartTime;
2137                 if (sDebug) {
2138                     final StringBuilder msg = new StringBuilder("1st UI for ")
2139                             .append(mActivityToken)
2140                             .append(" shown in ");
2141                     TimeUtils.formatDuration(duration, msg);
2142                     Slog.d(TAG, msg.toString());
2143                 }
2144                 final StringBuilder historyLog = new StringBuilder("id=").append(id)
2145                         .append(" app=").append(mActivityToken)
2146                         .append(" svc=").append(mService.getServicePackageName())
2147                         .append(" latency=");
2148                 TimeUtils.formatDuration(duration, historyLog);
2149                 mUiLatencyHistory.log(historyLog.toString());
2150
2151                 addTaggedDataToRequestLogLocked(response.getRequestId(),
2152                         MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
2153             }
2154         }
2155     }
2156
2157     boolean isDestroyed() {
2158         synchronized (mLock) {
2159             return mDestroyed;
2160         }
2161     }
2162
2163     IAutoFillManagerClient getClient() {
2164         synchronized (mLock) {
2165             return mClient;
2166         }
2167     }
2168
2169     private void notifyUnavailableToClient(int sessionFinishedState) {
2170         synchronized (mLock) {
2171             if (mCurrentViewId == null) return;
2172             try {
2173                 if (mHasCallback) {
2174                     mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
2175                 } else if (sessionFinishedState != 0) {
2176                     mClient.setSessionFinished(sessionFinishedState);
2177                 }
2178             } catch (RemoteException e) {
2179                 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
2180             }
2181         }
2182     }
2183
2184     @GuardedBy("mLock")
2185     private void updateTrackedIdsLocked() {
2186         // Only track the views of the last response as only those are reported back to the
2187         // service, see #showSaveLocked
2188         final FillResponse response = getLastResponseLocked(null);
2189         if (response == null) return;
2190
2191         ArraySet<AutofillId> trackedViews = null;
2192         mSaveOnAllViewsInvisible = false;
2193         boolean saveOnFinish = true;
2194         final SaveInfo saveInfo = response.getSaveInfo();
2195         final AutofillId saveTriggerId;
2196         if (saveInfo != null) {
2197             saveTriggerId = saveInfo.getTriggerId();
2198             if (saveTriggerId != null) {
2199                 writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
2200             }
2201             mSaveOnAllViewsInvisible =
2202                     (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
2203
2204             // We only need to track views if we want to save once they become invisible.
2205             if (mSaveOnAllViewsInvisible) {
2206                 if (trackedViews == null) {
2207                     trackedViews = new ArraySet<>();
2208                 }
2209                 if (saveInfo.getRequiredIds() != null) {
2210                     Collections.addAll(trackedViews, saveInfo.getRequiredIds());
2211                 }
2212
2213                 if (saveInfo.getOptionalIds() != null) {
2214                     Collections.addAll(trackedViews, saveInfo.getOptionalIds());
2215                 }
2216             }
2217             if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
2218                 saveOnFinish = false;
2219             }
2220
2221         } else {
2222             saveTriggerId = null;
2223         }
2224
2225         // Must also track that are part of datasets, otherwise the FillUI won't be hidden when
2226         // they go away (if they're not savable).
2227
2228         final List<Dataset> datasets = response.getDatasets();
2229         ArraySet<AutofillId> fillableIds = null;
2230         if (datasets != null) {
2231             for (int i = 0; i < datasets.size(); i++) {
2232                 final Dataset dataset = datasets.get(i);
2233                 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
2234                 if (fieldIds == null) continue;
2235
2236                 for (int j = 0; j < fieldIds.size(); j++) {
2237                     final AutofillId id = fieldIds.get(j);
2238                     if (trackedViews == null || !trackedViews.contains(id)) {
2239                         fillableIds = ArrayUtils.add(fillableIds, id);
2240                     }
2241                 }
2242             }
2243         }
2244
2245         try {
2246             if (sVerbose) {
2247                 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
2248                         + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish);
2249             }
2250             mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
2251                     saveOnFinish, toArray(fillableIds), saveTriggerId);
2252         } catch (RemoteException e) {
2253             Slog.w(TAG, "Cannot set tracked ids", e);
2254         }
2255     }
2256
2257     /**
2258      * Sets the state of views that failed to autofill.
2259      */
2260     @GuardedBy("mLock")
2261     void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
2262         for (int i = 0; i < ids.size(); i++) {
2263             final AutofillId id = ids.get(i);
2264             final ViewState viewState = mViewStates.get(id);
2265             if (viewState == null) {
2266                 Slog.w(TAG, "setAutofillFailure(): no view for id " + id);
2267                 continue;
2268             }
2269             viewState.resetState(ViewState.STATE_AUTOFILLED);
2270             final int state = viewState.getState();
2271             viewState.setState(state | ViewState.STATE_AUTOFILL_FAILED);
2272             if (sVerbose) {
2273                 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
2274             }
2275         }
2276     }
2277
2278     @GuardedBy("mLock")
2279     private void replaceResponseLocked(@NonNull FillResponse oldResponse,
2280             @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
2281         // Disassociate view states with the old response
2282         setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
2283         // Move over the id
2284         newResponse.setRequestId(oldResponse.getRequestId());
2285         // Replace the old response
2286         mResponses.put(newResponse.getRequestId(), newResponse);
2287         // Now process the new response
2288         processResponseLocked(newResponse, newClientState, 0);
2289     }
2290
2291     private void processNullResponseLocked(int flags) {
2292         if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null");
2293         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
2294             getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
2295         }
2296         mService.resetLastResponse();
2297         // Nothing to be done, but need to notify client.
2298         notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
2299         removeSelf();
2300     }
2301
2302     @GuardedBy("mLock")
2303     private void processResponseLocked(@NonNull FillResponse newResponse,
2304             @Nullable Bundle newClientState, int flags) {
2305         // Make sure we are hiding the UI which will be shown
2306         // only if handling the current response requires it.
2307         mUi.hideAll(this);
2308
2309         final int requestId = newResponse.getRequestId();
2310         if (sVerbose) {
2311             Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
2312                     + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
2313                     + ",newClientState=" + newClientState);
2314         }
2315
2316         if (mResponses == null) {
2317             // Set initial capacity as 2 to handle cases where service always requires auth.
2318             // TODO: add a metric for number of responses set by server, so we can use its average
2319             // as the initial array capacitiy.
2320             mResponses = new SparseArray<>(2);
2321         }
2322         mResponses.put(requestId, newResponse);
2323         mClientState = newClientState != null ? newClientState : newResponse.getClientState();
2324
2325         setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
2326         updateTrackedIdsLocked();
2327
2328         if (mCurrentViewId == null) {
2329             return;
2330         }
2331
2332         // Updates the UI, if necessary.
2333         final ViewState currentView = mViewStates.get(mCurrentViewId);
2334         currentView.maybeCallOnFillReady(flags);
2335     }
2336
2337     /**
2338      * Sets the state of all views in the given response.
2339      */
2340     @GuardedBy("mLock")
2341     private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
2342         final List<Dataset> datasets = response.getDatasets();
2343         if (datasets != null) {
2344             for (int i = 0; i < datasets.size(); i++) {
2345                 final Dataset dataset = datasets.get(i);
2346                 if (dataset == null) {
2347                     Slog.w(TAG, "Ignoring null dataset on " + datasets);
2348                     continue;
2349                 }
2350                 setViewStatesLocked(response, dataset, state, clearResponse);
2351             }
2352         } else if (response.getAuthentication() != null) {
2353             for (AutofillId autofillId : response.getAuthenticationIds()) {
2354                 final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null);
2355                 if (!clearResponse) {
2356                     viewState.setResponse(response);
2357                 } else {
2358                     viewState.setResponse(null);
2359                 }
2360             }
2361         }
2362         final SaveInfo saveInfo = response.getSaveInfo();
2363         if (saveInfo != null) {
2364             final AutofillId[] requiredIds = saveInfo.getRequiredIds();
2365             if (requiredIds != null) {
2366                 for (AutofillId id : requiredIds) {
2367                     createOrUpdateViewStateLocked(id, state, null);
2368                 }
2369             }
2370             final AutofillId[] optionalIds = saveInfo.getOptionalIds();
2371             if (optionalIds != null) {
2372                 for (AutofillId id : optionalIds) {
2373                     createOrUpdateViewStateLocked(id, state, null);
2374                 }
2375             }
2376         }
2377
2378         final AutofillId[] authIds = response.getAuthenticationIds();
2379         if (authIds != null) {
2380             for (AutofillId id : authIds) {
2381                 createOrUpdateViewStateLocked(id, state, null);
2382             }
2383         }
2384     }
2385
2386     /**
2387      * Sets the state of all views in the given dataset and response.
2388      */
2389     @GuardedBy("mLock")
2390     private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
2391             int state, boolean clearResponse) {
2392         final ArrayList<AutofillId> ids = dataset.getFieldIds();
2393         final ArrayList<AutofillValue> values = dataset.getFieldValues();
2394         for (int j = 0; j < ids.size(); j++) {
2395             final AutofillId id = ids.get(j);
2396             final AutofillValue value = values.get(j);
2397             final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
2398             final String datasetId = dataset.getId();
2399             if (datasetId != null) {
2400                 viewState.setDatasetId(datasetId);
2401             }
2402             if (response != null) {
2403                 viewState.setResponse(response);
2404             } else if (clearResponse) {
2405                 viewState.setResponse(null);
2406             }
2407         }
2408     }
2409
2410     @GuardedBy("mLock")
2411     private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
2412             @Nullable AutofillValue value) {
2413         ViewState viewState = mViewStates.get(id);
2414         if (viewState != null)  {
2415             viewState.setState(state);
2416         } else {
2417             viewState = new ViewState(this, id, this, state);
2418             if (sVerbose) {
2419                 Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state);
2420             }
2421             mViewStates.put(id, viewState);
2422         }
2423         if ((state & ViewState.STATE_AUTOFILLED) != 0) {
2424             viewState.setAutofilledValue(value);
2425         }
2426         return viewState;
2427     }
2428
2429     void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
2430         if (sDebug) {
2431             Slog.d(TAG, "autoFill(): requestId=" + requestId  + "; datasetIdx=" + datasetIndex
2432                     + "; dataset=" + dataset);
2433         }
2434         synchronized (mLock) {
2435             if (mDestroyed) {
2436                 Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
2437                         + id + " destroyed");
2438                 return;
2439             }
2440             // Autofill it directly...
2441             if (dataset.getAuthentication() == null) {
2442                 if (generateEvent) {
2443                     mService.logDatasetSelected(dataset.getId(), id, mClientState);
2444                 }
2445
2446                 autoFillApp(dataset);
2447                 return;
2448             }
2449
2450             // ...or handle authentication.
2451             mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
2452             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
2453             final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
2454             if (fillInIntent == null) {
2455                 forceRemoveSelfLocked();
2456                 return;
2457             }
2458             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
2459                     datasetIndex);
2460             startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
2461
2462         }
2463     }
2464
2465     CharSequence getServiceName() {
2466         synchronized (mLock) {
2467             return mService.getServiceName();
2468         }
2469     }
2470
2471     // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
2472     @GuardedBy("mLock")
2473     @Nullable
2474     private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
2475         final Intent fillInIntent = new Intent();
2476
2477         final FillContext context = getFillContextByRequestIdLocked(requestId);
2478
2479         if (context == null) {
2480             wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s",
2481                     requestId, mContexts);
2482             return null;
2483         }
2484         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
2485         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
2486         return fillInIntent;
2487     }
2488
2489     private void startAuthentication(int authenticationId, IntentSender intent,
2490             Intent fillInIntent) {
2491         try {
2492             synchronized (mLock) {
2493                 mClient.authenticate(id, authenticationId, intent, fillInIntent);
2494             }
2495         } catch (RemoteException e) {
2496             Slog.e(TAG, "Error launching auth intent", e);
2497         }
2498     }
2499
2500     @Override
2501     public String toString() {
2502         return "Session: [id=" + id + ", component=" + mComponentName + "]";
2503     }
2504
2505     @GuardedBy("mLock")
2506     void dumpLocked(String prefix, PrintWriter pw) {
2507         final String prefix2 = prefix + "  ";
2508         pw.print(prefix); pw.print("id: "); pw.println(id);
2509         pw.print(prefix); pw.print("uid: "); pw.println(uid);
2510         pw.print(prefix); pw.print("flags: "); pw.println(mFlags);
2511         pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
2512         pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
2513         pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
2514         pw.print(prefix); pw.print("Time to show UI: ");
2515         if (mUiShownTime == 0) {
2516             pw.println("N/A");
2517         } else {
2518             TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
2519             pw.println();
2520         }
2521         final int requestLogsSizes = mRequestLogs.size();
2522         pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes);
2523         for (int i = 0; i < requestLogsSizes; i++) {
2524             final int requestId = mRequestLogs.keyAt(i);
2525             final LogMaker log = mRequestLogs.valueAt(i);
2526             pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req=");
2527             pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println();
2528         }
2529         pw.print(prefix); pw.print("mResponses: ");
2530         if (mResponses == null) {
2531             pw.println("null");
2532         } else {
2533             pw.println(mResponses.size());
2534             for (int i = 0; i < mResponses.size(); i++) {
2535                 pw.print(prefix2); pw.print('#'); pw.print(i);
2536                 pw.print(' '); pw.println(mResponses.valueAt(i));
2537             }
2538         }
2539         pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
2540         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
2541         pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving);
2542         pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi);
2543         final int numberViews = mViewStates.size();
2544         pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
2545         for (int i = 0; i < numberViews; i++) {
2546             pw.print(prefix); pw.print("ViewState at #"); pw.println(i);
2547             mViewStates.valueAt(i).dump(prefix2, pw);
2548         }
2549
2550         pw.print(prefix); pw.print("mContexts: " );
2551         if (mContexts != null) {
2552             int numContexts = mContexts.size();
2553             for (int i = 0; i < numContexts; i++) {
2554                 FillContext context = mContexts.get(i);
2555
2556                 pw.print(prefix2); pw.print(context);
2557                 if (sVerbose) {
2558                     pw.println("AssistStructure dumped at logcat)");
2559
2560                     // TODO: add method on AssistStructure to dump on pw
2561                     context.getStructure().dump(false);
2562                 }
2563             }
2564         } else {
2565             pw.println("null");
2566         }
2567
2568         pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
2569         if (mClientState != null) {
2570             pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw
2571                 .println(" bytes");
2572         }
2573         pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode);
2574         pw.print(prefix); pw.print("mUrlBar: ");
2575         if (mUrlBar == null) {
2576             pw.println("N/A");
2577         } else {
2578             pw.print("id="); pw.print(mUrlBar.getAutofillId());
2579             pw.print(" domain="); pw.print(mUrlBar.getWebDomain());
2580             pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText());
2581         }
2582         pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println(
2583                 mSaveOnAllViewsInvisible);
2584         pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds);
2585         mRemoteFillService.dump(prefix, pw);
2586     }
2587
2588     private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) {
2589         pw.print("CAT="); pw.print(log.getCategory());
2590         pw.print(", TYPE=");
2591         final int type = log.getType();
2592         switch (type) {
2593             case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break;
2594             case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break;
2595             case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break;
2596             default: pw.print("UNSUPPORTED");
2597         }
2598         pw.print('('); pw.print(type); pw.print(')');
2599         pw.print(", PKG="); pw.print(log.getPackageName());
2600         pw.print(", SERVICE="); pw.print(log
2601                 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE));
2602         pw.print(", ORDINAL="); pw.print(log
2603                 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL));
2604         dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS);
2605         dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS);
2606         dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION);
2607         final int authStatus =
2608                 getNumericValue(log, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS);
2609         if (authStatus != 0) {
2610             pw.print(", AUTH_STATUS=");
2611             switch (authStatus) {
2612                 case MetricsEvent.AUTOFILL_AUTHENTICATED:
2613                     pw.print("AUTHENTICATED"); break;
2614                 case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED:
2615                     pw.print("DATASET_AUTHENTICATED"); break;
2616                 case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION:
2617                     pw.print("INVALID_AUTHENTICATION"); break;
2618                 case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION:
2619                     pw.print("INVALID_DATASET_AUTHENTICATION"); break;
2620                 default: pw.print("UNSUPPORTED");
2621             }
2622             pw.print('('); pw.print(authStatus); pw.print(')');
2623         }
2624         dumpNumericValue(pw, log, "FC_IDS",
2625                 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS);
2626         dumpNumericValue(pw, log, "COMPAT_MODE",
2627                 MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE);
2628     }
2629
2630     private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log,
2631             @NonNull String field, int tag) {
2632         final int value = getNumericValue(log, tag);
2633         if (value != 0) {
2634             pw.print(", "); pw.print(field); pw.print('='); pw.print(value);
2635         }
2636     }
2637
2638     void autoFillApp(Dataset dataset) {
2639         synchronized (mLock) {
2640             if (mDestroyed) {
2641                 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: "
2642                         + id + " destroyed");
2643                 return;
2644             }
2645             try {
2646                 // Skip null values as a null values means no change
2647                 final int entryCount = dataset.getFieldIds().size();
2648                 final List<AutofillId> ids = new ArrayList<>(entryCount);
2649                 final List<AutofillValue> values = new ArrayList<>(entryCount);
2650                 boolean waitingDatasetAuth = false;
2651                 for (int i = 0; i < entryCount; i++) {
2652                     if (dataset.getFieldValues().get(i) == null) {
2653                         continue;
2654                     }
2655                     final AutofillId viewId = dataset.getFieldIds().get(i);
2656                     ids.add(viewId);
2657                     values.add(dataset.getFieldValues().get(i));
2658                     final ViewState viewState = mViewStates.get(viewId);
2659                     if (viewState != null
2660                             && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) {
2661                         if (sVerbose) {
2662                             Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth");
2663                         }
2664                         waitingDatasetAuth = true;
2665                         viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH);
2666                     }
2667                 }
2668                 if (!ids.isEmpty()) {
2669                     if (waitingDatasetAuth) {
2670                         mUi.hideFillUi(this);
2671                     }
2672                     if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
2673
2674                     mClient.autofill(id, ids, values);
2675                     if (dataset.getId() != null) {
2676                         if (mSelectedDatasetIds == null) {
2677                             mSelectedDatasetIds = new ArrayList<>();
2678                         }
2679                         mSelectedDatasetIds.add(dataset.getId());
2680                     }
2681                     setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false);
2682                 }
2683             } catch (RemoteException e) {
2684                 Slog.w(TAG, "Error autofilling activity: " + e);
2685             }
2686         }
2687     }
2688
2689     private AutoFillUI getUiForShowing() {
2690         synchronized (mLock) {
2691             mUi.setCallback(this);
2692             return mUi;
2693         }
2694     }
2695
2696     /**
2697      * Cleans up this session.
2698      *
2699      * <p>Typically called in 2 scenarios:
2700      *
2701      * <ul>
2702      *   <li>When the session naturally finishes (i.e., from {@link #removeSelfLocked()}.
2703      *   <li>When the service hosting the session is finished (for example, because the user
2704      *       disabled it).
2705      * </ul>
2706      */
2707     @GuardedBy("mLock")
2708     RemoteFillService destroyLocked() {
2709         if (mDestroyed) {
2710             return null;
2711         }
2712         unlinkClientVultureLocked();
2713         mUi.destroyAll(mPendingSaveUi, this, true);
2714         mUi.clearCallback(this);
2715         mDestroyed = true;
2716
2717         // Log metrics
2718         final int totalRequests = mRequestLogs.size();
2719         if (totalRequests > 0) {
2720             if (sVerbose) Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " requests");
2721             for (int i = 0; i < totalRequests; i++) {
2722                 final LogMaker log = mRequestLogs.valueAt(i);
2723                 mMetricsLogger.write(log);
2724             }
2725         }
2726         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
2727                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests));
2728
2729         return mRemoteFillService;
2730     }
2731
2732     /**
2733      * Cleans up this session and remove it from the service always, even if it does have a pending
2734      * Save UI.
2735      */
2736     @GuardedBy("mLock")
2737     void forceRemoveSelfLocked() {
2738         forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN);
2739     }
2740
2741     @GuardedBy("mLock")
2742     void forceRemoveSelfLocked(int clientState) {
2743         if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
2744
2745         final boolean isPendingSaveUi = isSaveUiPendingLocked();
2746         mPendingSaveUi = null;
2747         removeSelfLocked();
2748         mUi.destroyAll(mPendingSaveUi, this, false);
2749         if (!isPendingSaveUi) {
2750             try {
2751                 mClient.setSessionFinished(clientState);
2752             } catch (RemoteException e) {
2753                 Slog.e(TAG, "Error notifying client to finish session", e);
2754             }
2755         }
2756     }
2757
2758     /**
2759      * Thread-safe version of {@link #removeSelfLocked()}.
2760      */
2761     private void removeSelf() {
2762         synchronized (mLock) {
2763             removeSelfLocked();
2764         }
2765     }
2766
2767     /**
2768      * Cleans up this session and remove it from the service, but but only if it does not have a
2769      * pending Save UI.
2770      */
2771     @GuardedBy("mLock")
2772     void removeSelfLocked() {
2773         if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
2774         if (mDestroyed) {
2775             Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: "
2776                     + id + " destroyed");
2777             return;
2778         }
2779         if (isSaveUiPendingLocked()) {
2780             Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
2781             return;
2782         }
2783
2784         final RemoteFillService remoteFillService = destroyLocked();
2785         mService.removeSessionLocked(id);
2786         if (remoteFillService != null) {
2787             remoteFillService.destroy();
2788         }
2789     }
2790
2791     void onPendingSaveUi(int operation, @NonNull IBinder token) {
2792         getUiForShowing().onPendingSaveUi(operation, token);
2793     }
2794
2795     /**
2796      * Checks whether this session is hiding the Save UI to handle a custom description link for
2797      * a specific {@code token} created by
2798      * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
2799      */
2800     @GuardedBy("mLock")
2801     boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
2802         return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
2803     }
2804
2805     /**
2806      * Checks whether this session is hiding the Save UI to handle a custom description link.
2807      */
2808     @GuardedBy("mLock")
2809     private boolean isSaveUiPendingLocked() {
2810         return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
2811     }
2812
2813     @GuardedBy("mLock")
2814     private int getLastResponseIndexLocked() {
2815         // The response ids are monotonically increasing so
2816         // we just find the largest id which is the last. We
2817         // do not rely on the internal ordering in sparse
2818         // array to avoid - wow this stopped working!?
2819         int lastResponseIdx = -1;
2820         int lastResponseId = -1;
2821         if (mResponses != null) {
2822             final int responseCount = mResponses.size();
2823             for (int i = 0; i < responseCount; i++) {
2824                 if (mResponses.keyAt(i) > lastResponseId) {
2825                     lastResponseIdx = i;
2826                 }
2827             }
2828         }
2829         return lastResponseIdx;
2830     }
2831
2832     private LogMaker newLogMaker(int category) {
2833         return newLogMaker(category, mService.getServicePackageName());
2834     }
2835
2836     private LogMaker newLogMaker(int category, String servicePackageName) {
2837         return Helper.newLogMaker(category, mComponentName, servicePackageName, id, mCompatMode);
2838     }
2839
2840     private void writeLog(int category) {
2841         mMetricsLogger.write(newLogMaker(category));
2842     }
2843
2844     private void logAuthenticationStatusLocked(int requestId, int status) {
2845         addTaggedDataToRequestLogLocked(requestId,
2846                 MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status);
2847     }
2848
2849     private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) {
2850         final LogMaker requestLog = mRequestLogs.get(requestId);
2851         if (requestLog == null) {
2852             Slog.w(TAG,
2853                     "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId);
2854             return;
2855         }
2856         requestLog.addTaggedData(tag, value);
2857     }
2858
2859     private static String requestLogToString(@NonNull LogMaker log) {
2860         final StringWriter sw = new StringWriter();
2861         final PrintWriter pw = new PrintWriter(sw);
2862         dumpRequestLog(pw, log);
2863         pw.flush();
2864         return sw.toString();
2865     }
2866
2867     private void wtf(@Nullable Exception e, String fmt, Object...args) {
2868         final String message = String.format(fmt, args);
2869         mWtfHistory.log(message);
2870
2871         if (e != null) {
2872             Slog.wtf(TAG, message, e);
2873         } else {
2874             Slog.wtf(TAG, message);
2875         }
2876     }
2877
2878     private static String actionAsString(int action) {
2879         switch (action) {
2880             case ACTION_START_SESSION:
2881                 return "START_SESSION";
2882             case ACTION_VIEW_ENTERED:
2883                 return "VIEW_ENTERED";
2884             case ACTION_VIEW_EXITED:
2885                 return "VIEW_EXITED";
2886             case ACTION_VALUE_CHANGED:
2887                 return "VALUE_CHANGED";
2888             default:
2889                 return "UNKNOWN_" + action;
2890         }
2891     }
2892 }