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