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