Session.java revision 627cc3c7ae03d46f775a0e681034c38ad0867bd7
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.autofill;
18
19import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
20import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
21import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
22import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
23import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
24import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
25import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
26import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
27
28import static com.android.server.autofill.Helper.sDebug;
29import static com.android.server.autofill.Helper.sPartitionMaxCount;
30import static com.android.server.autofill.Helper.sVerbose;
31import static com.android.server.autofill.Helper.toArray;
32import static com.android.server.autofill.ViewState.STATE_AUTOFILLED;
33import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION;
34
35import android.annotation.NonNull;
36import android.annotation.Nullable;
37import android.app.Activity;
38import android.app.ActivityManager;
39import android.app.assist.AssistStructure;
40import android.app.assist.AssistStructure.AutofillOverlay;
41import android.app.assist.AssistStructure.ViewNode;
42import android.content.ComponentName;
43import android.content.Context;
44import android.content.Intent;
45import android.content.IntentSender;
46import android.graphics.Rect;
47import android.metrics.LogMaker;
48import android.os.Binder;
49import android.os.Bundle;
50import android.os.IBinder;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.service.autofill.AutofillService;
54import android.service.autofill.Dataset;
55import android.service.autofill.FillContext;
56import android.service.autofill.FillRequest;
57import android.service.autofill.FillResponse;
58import android.service.autofill.InternalValidator;
59import android.service.autofill.SaveInfo;
60import android.service.autofill.SaveRequest;
61import android.service.autofill.ValueFinder;
62import android.util.ArrayMap;
63import android.util.ArraySet;
64import android.util.Slog;
65import android.util.SparseArray;
66import android.view.autofill.AutofillId;
67import android.view.autofill.AutofillManager;
68import android.view.autofill.AutofillValue;
69import android.view.autofill.IAutoFillManagerClient;
70import android.view.autofill.IAutofillWindowPresenter;
71
72import com.android.internal.R;
73import com.android.internal.annotations.GuardedBy;
74import com.android.internal.logging.MetricsLogger;
75import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
76import com.android.internal.os.HandlerCaller;
77import com.android.internal.os.IResultReceiver;
78import com.android.internal.util.ArrayUtils;
79import com.android.server.autofill.ui.AutoFillUI;
80import com.android.server.autofill.ui.PendingUi;
81
82import java.io.PrintWriter;
83import java.util.ArrayList;
84import java.util.Arrays;
85import java.util.Collections;
86import java.util.List;
87import java.util.Map;
88import java.util.concurrent.atomic.AtomicInteger;
89
90/**
91 * A session for a given activity.
92 *
93 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
94 * of the current {@link ViewState} to display the appropriate UI.
95 *
96 * <p>Although the autofill requests and callbacks are stateless from the service's point of
97 * view, we need to keep state in the framework side for cases such as authentication. For
98 * example, when service return a {@link FillResponse} that contains all the fields needed
99 * to fill the activity but it requires authentication first, that response need to be held
100 * until the user authenticates or it times out.
101 */
102final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
103        AutoFillUI.AutoFillUiCallback {
104    private static final String TAG = "AutofillSession";
105
106    private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
107
108    private final AutofillManagerServiceImpl mService;
109    private final HandlerCaller mHandlerCaller;
110    private final Object mLock;
111    private final AutoFillUI mUi;
112
113    private final MetricsLogger mMetricsLogger = new MetricsLogger();
114
115    private static AtomicInteger sIdCounter = new AtomicInteger();
116
117    /** Id of the session */
118    public final int id;
119
120    /** uid the session is for */
121    public final int uid;
122
123    @GuardedBy("mLock")
124    @NonNull private IBinder mActivityToken;
125
126    /** Package name of the app that is auto-filled */
127    @NonNull private final String mPackageName;
128
129    @GuardedBy("mLock")
130    private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
131
132    /**
133     * Id of the View currently being displayed.
134     */
135    @GuardedBy("mLock")
136    @Nullable private AutofillId mCurrentViewId;
137
138    @GuardedBy("mLock")
139    private IAutoFillManagerClient mClient;
140
141    private final RemoteFillService mRemoteFillService;
142
143    @GuardedBy("mLock")
144    private SparseArray<FillResponse> mResponses;
145
146    /**
147     * Contexts read from the app; they will be updated (sanitized, change values for save) before
148     * sent to {@link AutofillService}. Ordered by the time they we read.
149     */
150    @GuardedBy("mLock")
151    private ArrayList<FillContext> mContexts;
152
153    /**
154     * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
155     */
156    private boolean mHasCallback;
157
158    /**
159     * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved
160     * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
161     */
162    @GuardedBy("mLock")
163    private Bundle mClientState;
164
165    @GuardedBy("mLock")
166    private boolean mDestroyed;
167
168    /** Whether the session is currently saving. */
169    @GuardedBy("mLock")
170    private boolean mIsSaving;
171
172    /**
173     * Helper used to handle state of Save UI when it must be hiding to show a custom description
174     * link and later recovered.
175     */
176    @GuardedBy("mLock")
177    private PendingUi mPendingSaveUi;
178
179    /**
180     * Receiver of assist data from the app's {@link Activity}.
181     */
182    private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
183        @Override
184        public void send(int resultCode, Bundle resultData) throws RemoteException {
185            final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
186            if (structure == null) {
187                Slog.e(TAG, "No assist structure - app might have crashed providing it");
188                return;
189            }
190
191            final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
192            if (receiverExtras == null) {
193                Slog.e(TAG, "No receiver extras - app might have crashed providing it");
194                return;
195            }
196
197            final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
198
199            if (sVerbose) {
200                Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure);
201            }
202
203            final FillRequest request;
204            synchronized (mLock) {
205                // TODO(b/35708678): Must fetch the data so it's available later on handleSave(),
206                // even if if the activity is gone by then, but structure .ensureData() gives a
207                // ONE_WAY warning because system_service could block on app calls. We need to
208                // change AssistStructure so it provides a "one-way" writeToParcel() method that
209                // sends all the data
210                structure.ensureData();
211
212                // Sanitize structure before it's sent to service.
213                structure.sanitizeForParceling(true);
214
215                // Flags used to start the session.
216                final int flags = structure.getFlags();
217
218                if (mContexts == null) {
219                    mContexts = new ArrayList<>(1);
220                }
221                mContexts.add(new FillContext(requestId, structure));
222
223                cancelCurrentRequestLocked();
224
225                final int numContexts = mContexts.size();
226                for (int i = 0; i < numContexts; i++) {
227                    fillContextWithAllowedValuesLocked(mContexts.get(i), flags);
228                }
229
230                // Dispatch a snapshot of the current contexts list since it may change
231                // until the dispatch happens. The items in the list don't need to be cloned
232                // since we don't hold on them anywhere else. The client state is not touched
233                // by us, so no need to copy.
234                request = new FillRequest(requestId, new ArrayList<>(mContexts),
235                        mClientState, flags);
236            }
237
238            mRemoteFillService.onFillRequest(request);
239        }
240    };
241
242    /**
243     * Returns the ids of all entries in {@link #mViewStates} in the same order.
244     */
245    private AutofillId[] getIdsOfAllViewStatesLocked() {
246        final int numViewState = mViewStates.size();
247        final AutofillId[] ids = new AutofillId[numViewState];
248        for (int i = 0; i < numViewState; i++) {
249            ids[i] = mViewStates.valueAt(i).id;
250        }
251
252        return ids;
253    }
254
255    /**
256     * Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, or
257     * {@code null} when not found on either of them.
258     */
259    @Nullable
260    private String getValueAsString(@NonNull AutofillId id) {
261        AutofillValue value = null;
262        synchronized (mLock) {
263            final ViewState state = mViewStates.get(id);
264            if (state == null) {
265                if (sDebug) Slog.d(TAG, "getValue(): no view state for " + id);
266                return null;
267            }
268            value = state.getCurrentValue();
269            if (value == null) {
270                if (sDebug) Slog.d(TAG, "getValue(): no current value for " + id);
271                value = getValueFromContextsLocked(id);
272            }
273        }
274        if (value != null) {
275            if (value.isText()) {
276                return value.getTextValue().toString();
277            }
278            if (value.isList()) {
279                final CharSequence[] options = getAutofillOptionsFromContextsLocked(id);
280                if (options != null) {
281                    final int index = value.getListValue();
282                    final CharSequence option = options[index];
283                    return option != null ? option.toString() : null;
284                } else {
285                    Slog.w(TAG, "getValueAsString(): no autofill options for id " + id);
286                }
287            }
288        }
289        return null;
290    }
291
292    /**
293     * Updates values of the nodes in the context's structure so that:
294     * - proper node is focused
295     * - autofillValue is sent back to service when it was previously autofilled
296     * - autofillValue is sent in the view used to force a request
297     *
298     * @param fillContext The context to be filled
299     * @param flags The flags that started the session
300     */
301    private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
302        final ViewNode[] nodes = fillContext
303                .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
304
305        final int numViewState = mViewStates.size();
306        for (int i = 0; i < numViewState; i++) {
307            final ViewState viewState = mViewStates.valueAt(i);
308
309            final ViewNode node = nodes[i];
310            if (node == null) {
311                if (sVerbose) {
312                    Slog.v(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id);
313                }
314                continue;
315            }
316
317            final AutofillValue currentValue = viewState.getCurrentValue();
318            final AutofillValue filledValue = viewState.getAutofilledValue();
319            final AutofillOverlay overlay = new AutofillOverlay();
320
321            // Sanitizes the value if the current value matches what the service sent.
322            if (filledValue != null && filledValue.equals(currentValue)) {
323                overlay.value = currentValue;
324            }
325
326            if (mCurrentViewId != null) {
327                // Updates the focus value.
328                overlay.focused = mCurrentViewId.equals(viewState.id);
329                // Sanitizes the value of the focused field in a manual request.
330                if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) {
331                    overlay.value = currentValue;
332                }
333            }
334            node.setAutofillOverlay(overlay);
335        }
336    }
337
338    /**
339     * Cancels the last request sent to the {@link #mRemoteFillService}.
340     */
341    private void cancelCurrentRequestLocked() {
342        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
343
344        // Remove the FillContext as there will never be a response for the service
345        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
346            final int numContexts = mContexts.size();
347
348            // It is most likely the last context, hence search backwards
349            for (int i = numContexts - 1; i >= 0; i--) {
350                if (mContexts.get(i).getRequestId() == canceledRequest) {
351                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
352                    mContexts.remove(i);
353                    break;
354                }
355            }
356        }
357    }
358
359    /**
360     * Reads a new structure and then request a new fill response from the fill service.
361     */
362    private void requestNewFillResponseLocked(int flags) {
363        int requestId;
364
365        do {
366            requestId = sIdCounter.getAndIncrement();
367        } while (requestId == INVALID_REQUEST_ID);
368
369        if (sVerbose) {
370            Slog.v(TAG, "Requesting structure for requestId=" + requestId + ", flags=" + flags);
371        }
372
373        // If the focus changes very quickly before the first request is returned each focus change
374        // triggers a new partition and we end up with many duplicate partitions. This is
375        // enhanced as the focus change can be much faster than the taking of the assist structure.
376        // Hence remove the currently queued request and replace it with the one queued after the
377        // structure is taken. This causes only one fill request per bust of focus changes.
378        cancelCurrentRequestLocked();
379
380        try {
381            final Bundle receiverExtras = new Bundle();
382            receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
383            final long identity = Binder.clearCallingIdentity();
384            try {
385                if (!ActivityManager.getService().requestAutofillData(mAssistReceiver,
386                        receiverExtras, mActivityToken, flags)) {
387                    Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
388                }
389            } finally {
390                Binder.restoreCallingIdentity(identity);
391            }
392        } catch (RemoteException e) {
393            // Should not happen, it's a local call.
394        }
395    }
396
397    Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
398            @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
399            @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
400            @NonNull IBinder client, boolean hasCallback,
401            @NonNull ComponentName componentName, @NonNull String packageName) {
402        id = sessionId;
403        this.uid = uid;
404        mService = service;
405        mLock = lock;
406        mUi = ui;
407        mHandlerCaller = handlerCaller;
408        mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
409        mActivityToken = activityToken;
410        mHasCallback = hasCallback;
411        mPackageName = packageName;
412        mClient = IAutoFillManagerClient.Stub.asInterface(client);
413
414        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
415    }
416
417    /**
418     * Gets the currently registered activity token
419     *
420     * @return The activity token
421     */
422    @NonNull IBinder getActivityTokenLocked() {
423        return mActivityToken;
424    }
425
426    /**
427     * Sets new activity and client for this session.
428     *
429     * @param newActivity The token of the new activity
430     * @param newClient The client receiving autofill callbacks
431     */
432    void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) {
433        synchronized (mLock) {
434            if (mDestroyed) {
435                Slog.w(TAG, "Call to Session#switchActivity() rejected - session: "
436                        + id + " destroyed");
437                return;
438            }
439            mActivityToken = newActivity;
440            mClient = IAutoFillManagerClient.Stub.asInterface(newClient);
441
442            // The tracked id are not persisted in the client, hence update them
443            updateTrackedIdsLocked();
444        }
445    }
446
447    // FillServiceCallbacks
448    @Override
449    public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
450            int serviceUid, @NonNull String servicePackageName) {
451        synchronized (mLock) {
452            if (mDestroyed) {
453                Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
454                        + id + " destroyed");
455                return;
456            }
457        }
458        if (response == null) {
459            processNullResponseLocked(requestFlags);
460            return;
461        }
462
463        mService.setLastResponse(serviceUid, id, response);
464
465        if ((response.getDatasets() == null || response.getDatasets().isEmpty())
466                        && response.getAuthentication() == null) {
467            // Response is "empty" from an UI point of view, need to notify client.
468            notifyUnavailableToClient();
469        }
470        synchronized (mLock) {
471            processResponseLocked(response, requestFlags);
472        }
473
474        final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
475                .setType(MetricsEvent.TYPE_SUCCESS)
476                .setPackageName(mPackageName)
477                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
478                        response.getDatasets() == null ? 0 : response.getDatasets().size())
479                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
480                        servicePackageName);
481        mMetricsLogger.write(log);
482    }
483
484    // FillServiceCallbacks
485    @Override
486    public void onFillRequestFailure(@Nullable CharSequence message,
487            @NonNull String servicePackageName) {
488        synchronized (mLock) {
489            if (mDestroyed) {
490                Slog.w(TAG, "Call to Session#onFillRequestFailure() rejected - session: "
491                        + id + " destroyed");
492                return;
493            }
494            mService.resetLastResponse();
495        }
496        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
497                .setType(MetricsEvent.TYPE_FAILURE)
498                .setPackageName(mPackageName)
499                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
500        mMetricsLogger.write(log);
501
502        getUiForShowing().showError(message, this);
503        removeSelf();
504    }
505
506    // FillServiceCallbacks
507    @Override
508    public void onSaveRequestSuccess(@NonNull String servicePackageName) {
509        synchronized (mLock) {
510            mIsSaving = false;
511
512            if (mDestroyed) {
513                Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
514                        + id + " destroyed");
515                return;
516            }
517        }
518        LogMaker log = (new LogMaker(
519                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
520                .setType(MetricsEvent.TYPE_SUCCESS)
521                .setPackageName(mPackageName)
522                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
523        mMetricsLogger.write(log);
524
525        // Nothing left to do...
526        removeSelf();
527    }
528
529    // FillServiceCallbacks
530    @Override
531    public void onSaveRequestFailure(@Nullable CharSequence message,
532            @NonNull String servicePackageName) {
533        synchronized (mLock) {
534            mIsSaving = false;
535
536            if (mDestroyed) {
537                Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
538                        + id + " destroyed");
539                return;
540            }
541        }
542        LogMaker log = (new LogMaker(
543                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
544                .setType(MetricsEvent.TYPE_FAILURE)
545                .setPackageName(mPackageName)
546                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
547        mMetricsLogger.write(log);
548
549        getUiForShowing().showError(message, this);
550        removeSelf();
551    }
552
553    /**
554     * Gets the {@link FillContext} for a request.
555     *
556     * @param requestId The id of the request
557     *
558     * @return The context or {@code null} if there is no context
559     */
560    @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
561        if (mContexts == null) {
562            return null;
563        }
564
565        int numContexts = mContexts.size();
566        for (int i = 0; i < numContexts; i++) {
567            FillContext context = mContexts.get(i);
568
569            if (context.getRequestId() == requestId) {
570                return context;
571            }
572        }
573
574        return null;
575    }
576
577    // FillServiceCallbacks
578    @Override
579    public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
580        final Intent fillInIntent;
581        synchronized (mLock) {
582            if (mDestroyed) {
583                Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
584                        + id + " destroyed");
585                return;
586            }
587            fillInIntent = createAuthFillInIntentLocked(requestId, extras);
588        }
589        mService.setAuthenticationSelected(id);
590
591        final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
592        mHandlerCaller.getHandler().post(() -> startAuthentication(authenticationId,
593                intent, fillInIntent));
594    }
595
596    // FillServiceCallbacks
597    @Override
598    public void onServiceDied(RemoteFillService service) {
599        // TODO(b/337565347): implement
600    }
601
602    // AutoFillUiCallback
603    @Override
604    public void fill(int requestId, int datasetIndex, Dataset dataset) {
605        synchronized (mLock) {
606            if (mDestroyed) {
607                Slog.w(TAG, "Call to Session#fill() rejected - session: "
608                        + id + " destroyed");
609                return;
610            }
611        }
612        mHandlerCaller.getHandler().post(() -> autoFill(requestId, datasetIndex, dataset, true));
613    }
614
615    // AutoFillUiCallback
616    @Override
617    public void save() {
618        synchronized (mLock) {
619            if (mDestroyed) {
620                Slog.w(TAG, "Call to Session#save() rejected - session: "
621                        + id + " destroyed");
622                return;
623            }
624        }
625        mHandlerCaller.getHandler()
626                .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, id, 0)
627                .sendToTarget();
628    }
629
630    // AutoFillUiCallback
631    @Override
632    public void cancelSave() {
633        synchronized (mLock) {
634            mIsSaving = false;
635
636            if (mDestroyed) {
637                Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
638                        + id + " destroyed");
639                return;
640            }
641        }
642        mHandlerCaller.getHandler().post(() -> removeSelf());
643    }
644
645    // AutoFillUiCallback
646    @Override
647    public void requestShowFillUi(AutofillId id, int width, int height,
648            IAutofillWindowPresenter presenter) {
649        synchronized (mLock) {
650            if (mDestroyed) {
651                Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: "
652                        + id + " destroyed");
653                return;
654            }
655            if (id.equals(mCurrentViewId)) {
656                try {
657                    final ViewState view = mViewStates.get(id);
658                    mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(),
659                            presenter);
660                } catch (RemoteException e) {
661                    Slog.e(TAG, "Error requesting to show fill UI", e);
662                }
663            } else {
664                if (sDebug) {
665                    Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view ("
666                            + mCurrentViewId + ") anymore");
667                }
668            }
669        }
670    }
671
672    // AutoFillUiCallback
673    @Override
674    public void requestHideFillUi(AutofillId id) {
675        synchronized (mLock) {
676            // NOTE: We allow this call in a destroyed state as the UI is
677            // asked to go away after we get destroyed, so let it do that.
678            try {
679                mClient.requestHideFillUi(this.id, id);
680            } catch (RemoteException e) {
681                Slog.e(TAG, "Error requesting to hide fill UI", e);
682            }
683        }
684    }
685
686    // AutoFillUiCallback
687    @Override
688    public void startIntentSender(IntentSender intentSender) {
689        synchronized (mLock) {
690            if (mDestroyed) {
691                Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: "
692                        + id + " destroyed");
693                return;
694            }
695            removeSelfLocked();
696        }
697        mHandlerCaller.getHandler().post(() -> {
698            try {
699                synchronized (mLock) {
700                    mClient.startIntentSender(intentSender, null);
701                }
702            } catch (RemoteException e) {
703                Slog.e(TAG, "Error launching auth intent", e);
704            }
705        });
706    }
707
708    void setAuthenticationResultLocked(Bundle data, int authenticationId) {
709        if (mDestroyed) {
710            Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
711                    + id + " destroyed");
712            return;
713        }
714        if (mResponses == null) {
715            // Typically happens when app explicitly called cancel() while the service was showing
716            // the auth UI.
717            Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
718            removeSelf();
719            return;
720        }
721        final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
722        final FillResponse authenticatedResponse = mResponses.get(requestId);
723        if (authenticatedResponse == null || data == null) {
724            removeSelf();
725            return;
726        }
727
728        final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(
729                authenticationId);
730        // Authenticated a dataset - reset view state regardless if we got a response or a dataset
731        if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
732            final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx);
733            if (dataset == null) {
734                removeSelf();
735                return;
736            }
737        }
738
739        final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
740        if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
741        if (result instanceof FillResponse) {
742            final FillResponse response = (FillResponse) result;
743            mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
744            replaceResponseLocked(authenticatedResponse, response);
745        } else if (result instanceof Dataset) {
746            // TODO: add proper metric
747            if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
748                final Dataset dataset = (Dataset) result;
749                authenticatedResponse.getDatasets().set(datasetIdx, dataset);
750                autoFill(requestId, datasetIdx, dataset, false);
751            }
752        } else {
753            if (result != null) {
754                Slog.w(TAG, "service returned invalid auth type: " + result);
755            }
756            // TODO: add proper metric (on else)
757            processNullResponseLocked(0);
758        }
759    }
760
761    void setHasCallbackLocked(boolean hasIt) {
762        if (mDestroyed) {
763            Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
764                    + id + " destroyed");
765            return;
766        }
767        mHasCallback = hasIt;
768    }
769
770    /**
771     * Shows the save UI, when session can be saved.
772     *
773     * @return {@code true} if session is done, or {@code false} if it's pending user action.
774     */
775    public boolean showSaveLocked() {
776        if (mDestroyed) {
777            Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
778                    + id + " destroyed");
779            return false;
780        }
781        if (mContexts == null) {
782            Slog.d(TAG, "showSaveLocked(): no contexts");
783            return true;
784        }
785        if (mResponses == null) {
786            // Happens when the activity / session was finished before the service replied, or
787            // when the service cannot autofill it (and returned a null response).
788            if (sVerbose) {
789                Slog.v(TAG, "showSaveLocked(): no responses on session");
790            }
791            return true;
792        }
793
794        final int lastResponseIdx = getLastResponseIndexLocked();
795        if (lastResponseIdx < 0) {
796            Slog.w(TAG, "showSaveLocked(): did not get last response. mResponses=" + mResponses
797                    + ", mViewStates=" + mViewStates);
798            return true;
799        }
800
801        final FillResponse response = mResponses.valueAt(lastResponseIdx);
802        final SaveInfo saveInfo = response.getSaveInfo();
803        if (sVerbose) {
804            Slog.v(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts
805                    + ", mViewStates=" + mViewStates);
806        }
807
808        /*
809         * The Save dialog is only shown if all conditions below are met:
810         *
811         * - saveInfo is not null.
812         * - autofillValue of all required ids is not null.
813         * - autofillValue of at least one id (required or optional) has changed.
814         * - there is no Dataset in the last FillResponse whose values of all dataset fields matches
815         *   the current values of all fields in the screen.
816         */
817        if (saveInfo == null) {
818            return true;
819        }
820
821        // Cache used to make sure changed fields do not belong to a dataset.
822        final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
823        final ArraySet<AutofillId> allIds = new ArraySet<>();
824
825        final AutofillId[] requiredIds = saveInfo.getRequiredIds();
826        boolean allRequiredAreNotEmpty = true;
827        boolean atLeastOneChanged = false;
828        if (requiredIds != null) {
829            for (int i = 0; i < requiredIds.length; i++) {
830                final AutofillId id = requiredIds[i];
831                if (id == null) {
832                    Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds));
833                    continue;
834                }
835                allIds.add(id);
836                final ViewState viewState = mViewStates.get(id);
837                if (viewState == null) {
838                    Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
839                    allRequiredAreNotEmpty = false;
840                    break;
841                }
842
843                AutofillValue value = viewState.getCurrentValue();
844                if (value == null || value.isEmpty()) {
845                    final AutofillValue initialValue = getValueFromContextsLocked(id);
846                    if (initialValue != null) {
847                        if (sDebug) {
848                            Slog.d(TAG, "Value of required field " + id + " didn't change; "
849                                    + "using initial value (" + initialValue + ") instead");
850                        }
851                        value = initialValue;
852                    } else {
853                        if (sDebug) {
854                            Slog.d(TAG, "empty value for required " + id );
855                        }
856                        allRequiredAreNotEmpty = false;
857                        break;
858                    }
859                }
860                currentValues.put(id, value);
861                final AutofillValue filledValue = viewState.getAutofilledValue();
862
863                if (!value.equals(filledValue)) {
864                    if (sDebug) {
865                        Slog.d(TAG, "found a change on required " + id + ": " + filledValue
866                                + " => " + value);
867                    }
868                    atLeastOneChanged = true;
869                }
870            }
871        }
872
873        final AutofillId[] optionalIds = saveInfo.getOptionalIds();
874        if (allRequiredAreNotEmpty) {
875            if (!atLeastOneChanged && optionalIds != null) {
876                // No change on required ids yet, look for changes on optional ids.
877                for (int i = 0; i < optionalIds.length; i++) {
878                    final AutofillId id = optionalIds[i];
879                    allIds.add(id);
880                    final ViewState viewState = mViewStates.get(id);
881                    if (viewState == null) {
882                        Slog.w(TAG, "no ViewState for optional " + id);
883                        continue;
884                    }
885                    if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
886                        final AutofillValue currentValue = viewState.getCurrentValue();
887                        currentValues.put(id, currentValue);
888                        final AutofillValue filledValue = viewState.getAutofilledValue();
889                        if (currentValue != null && !currentValue.equals(filledValue)) {
890                            if (sDebug) {
891                                Slog.d(TAG, "found a change on optional " + id + ": " + filledValue
892                                        + " => " + currentValue);
893                            }
894                            atLeastOneChanged = true;
895                            break;
896                        }
897                    } else {
898                        // Update current values cache based on initial value
899                        final AutofillValue initialValue = getValueFromContextsLocked(id);
900                        if (sDebug) {
901                            Slog.d(TAG, "no current value for " + id + "; initial value is "
902                                    + initialValue);
903                        }
904                        if (initialValue != null) {
905                            currentValues.put(id, initialValue);
906                        }
907                    }
908                }
909            }
910            if (atLeastOneChanged) {
911                if (sDebug) {
912                    Slog.d(TAG, "at least one field changed, validate fields for save UI");
913                }
914                final ValueFinder valueFinder = (id) -> {return getValueAsString(id);};
915
916                final InternalValidator validator = saveInfo.getValidator();
917                if (validator != null) {
918                    boolean isValid;
919                    try {
920                        isValid = validator.isValid(valueFinder);
921                    } catch (Exception e) {
922                        Slog.e(TAG, "Not showing save UI because of exception during validation "
923                                + e.getClass());
924                        return true;
925                    }
926
927                    if (!isValid) {
928                        Slog.i(TAG, "not showing save UI because fields failed validation");
929                        return true;
930                    }
931                }
932
933                // Make sure the service doesn't have the fields already by checking the datasets
934                // content.
935                final List<Dataset> datasets = response.getDatasets();
936                if (datasets != null) {
937                    datasets_loop: for (int i = 0; i < datasets.size(); i++) {
938                        final Dataset dataset = datasets.get(i);
939                        final ArrayMap<AutofillId, AutofillValue> datasetValues =
940                                Helper.getFields(dataset);
941                        if (sVerbose) {
942                            Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i
943                                    + ": " + dataset + "; allIds=" + allIds);
944                        }
945                        for (int j = 0; j < allIds.size(); j++) {
946                            final AutofillId id = allIds.valueAt(j);
947                            final AutofillValue currentValue = currentValues.get(id);
948                            if (currentValue == null) {
949                                if (sDebug) {
950                                    Slog.d(TAG, "dataset has value for field that is null: " + id);
951                                }
952                                continue datasets_loop;
953                            }
954                            final AutofillValue datasetValue = datasetValues.get(id);
955                            if (!currentValue.equals(datasetValue)) {
956                                if (sDebug) Slog.d(TAG, "found a change on id " + id);
957                                continue datasets_loop;
958                            }
959                            if (sVerbose) Slog.v(TAG, "no changes for id " + id);
960                        }
961                        if (sDebug) {
962                            Slog.d(TAG, "ignoring Save UI because all fields match contents of "
963                                    + "dataset #" + i + ": " + dataset);
964                        }
965                        return true;
966                    }
967                }
968
969                if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
970                mService.logSaveShown(id);
971                final IAutoFillManagerClient client = getClient();
972                mPendingSaveUi = new PendingUi(mActivityToken, id, client);
973                getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo,
974                        valueFinder, mPackageName, this, mPendingSaveUi);
975                if (client != null) {
976                    try {
977                        client.setSaveUiState(id, true);
978                    } catch (RemoteException e) {
979                        Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e);
980                    }
981                }
982                mIsSaving = true;
983                return false;
984            }
985        }
986        // Nothing changed...
987        if (sDebug) {
988            Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
989                    + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
990                    + ", atLeastOneChanged=" + atLeastOneChanged);
991        }
992        return true;
993    }
994
995    /**
996     * Returns whether the session is currently showing the save UI
997     */
998    boolean isSavingLocked() {
999        return mIsSaving;
1000    }
1001
1002    /**
1003     * Gets the latest non-empty value for the given id in the autofill contexts.
1004     */
1005    @Nullable
1006    private AutofillValue getValueFromContextsLocked(AutofillId id) {
1007        final int numContexts = mContexts.size();
1008        for (int i = numContexts - 1; i >= 0; i--) {
1009            final FillContext context = mContexts.get(i);
1010            final ViewNode node = context.findViewNodeByAutofillId(id);
1011            if (node != null) {
1012                final AutofillValue value = node.getAutofillValue();
1013                if (sDebug) {
1014                    Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + value);
1015                }
1016                if (value != null && !value.isEmpty()) {
1017                    return value;
1018                }
1019            }
1020        }
1021        return null;
1022    }
1023
1024    /**
1025     * Gets the latest autofill options for the given id in the autofill contexts.
1026     */
1027    @Nullable
1028    private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
1029        final int numContexts = mContexts.size();
1030
1031        for (int i = numContexts - 1; i >= 0; i--) {
1032            final FillContext context = mContexts.get(i);
1033            final ViewNode node = context.findViewNodeByAutofillId(id);
1034            if (node != null && node.getAutofillOptions() != null) {
1035                return node.getAutofillOptions();
1036            }
1037        }
1038        return null;
1039    }
1040
1041    /**
1042     * Calls service when user requested save.
1043     */
1044    void callSaveLocked() {
1045        if (mDestroyed) {
1046            Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
1047                    + id + " destroyed");
1048            return;
1049        }
1050
1051        if (sVerbose) Slog.v(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
1052
1053        if (mContexts == null) {
1054            Slog.w(TAG, "callSaveLocked(): no contexts");
1055            return;
1056        }
1057
1058        final int numContexts = mContexts.size();
1059
1060        for (int contextNum = 0; contextNum < numContexts; contextNum++) {
1061            final FillContext context = mContexts.get(contextNum);
1062
1063            final ViewNode[] nodes =
1064                context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
1065
1066            if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
1067
1068            for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
1069                final ViewState state = mViewStates.valueAt(viewStateNum);
1070
1071                final AutofillId id = state.id;
1072                final AutofillValue value = state.getCurrentValue();
1073                if (value == null) {
1074                    if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
1075                    continue;
1076                }
1077                final ViewNode node = nodes[viewStateNum];
1078                if (node == null) {
1079                    Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
1080                    continue;
1081                }
1082                if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
1083
1084                node.updateAutofillValue(value);
1085            }
1086
1087            // Sanitize structure before it's sent to service.
1088            context.getStructure().sanitizeForParceling(false);
1089
1090            if (sVerbose) {
1091                Slog.v(TAG, "Dumping structure of " + context + " before calling service.save()");
1092                context.getStructure().dump(false);
1093            }
1094        }
1095
1096        // Remove pending fill requests as the session is finished.
1097        cancelCurrentRequestLocked();
1098
1099        // Dispatch a snapshot of the current contexts list since it may change
1100        // until the dispatch happens. The items in the list don't need to be cloned
1101        // since we don't hold on them anywhere else. The client state is not touched
1102        // by us, so no need to copy.
1103        final SaveRequest saveRequest = new SaveRequest(new ArrayList<>(mContexts), mClientState);
1104        mRemoteFillService.onSaveRequest(saveRequest);
1105    }
1106
1107    /**
1108     * Starts (if necessary) a new fill request upon entering a view.
1109     *
1110     * <p>A new request will be started in 2 scenarios:
1111     * <ol>
1112     *   <li>If the user manually requested autofill after the view was already filled.
1113     *   <li>If the view is part of a new partition.
1114     * </ol>
1115     *
1116     * @param id The id of the view that is entered.
1117     * @param viewState The view that is entered.
1118     * @param flags The flag that was passed by the AutofillManager.
1119     */
1120    private void requestNewFillResponseIfNecessaryLocked(@NonNull AutofillId id,
1121            @NonNull ViewState viewState, int flags) {
1122        // First check if this is a manual request after view was autofilled.
1123        final int state = viewState.getState();
1124        final boolean restart = (state & STATE_AUTOFILLED) != 0
1125                && (flags & FLAG_MANUAL_REQUEST) != 0;
1126        if (restart) {
1127            if (sDebug) Slog.d(TAG, "Re-starting session on view  " + id);
1128            viewState.setState(STATE_RESTARTED_SESSION);
1129            requestNewFillResponseLocked(flags);
1130            return;
1131        }
1132
1133        // If it's not, then check if it it should start a partition.
1134        if (shouldStartNewPartitionLocked(id)) {
1135            if (sDebug) {
1136                Slog.d(TAG, "Starting partition for view id " + id + ": "
1137                        + viewState.getStateAsString());
1138            }
1139            viewState.setState(ViewState.STATE_STARTED_PARTITION);
1140            requestNewFillResponseLocked(flags);
1141        }
1142    }
1143
1144    /**
1145     * Determines if a new partition should be started for an id.
1146     *
1147     * @param id The id of the view that is entered
1148     *
1149     * @return {@code true} iff a new partition should be started
1150     */
1151    private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
1152        if (mResponses == null) {
1153            return true;
1154        }
1155
1156        final int numResponses = mResponses.size();
1157        if (numResponses >= sPartitionMaxCount) {
1158            Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
1159                    + " reached maximum of " + sPartitionMaxCount);
1160            return false;
1161        }
1162
1163        for (int responseNum = 0; responseNum < numResponses; responseNum++) {
1164            final FillResponse response = mResponses.valueAt(responseNum);
1165
1166            if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
1167                return false;
1168            }
1169
1170            final SaveInfo saveInfo = response.getSaveInfo();
1171            if (saveInfo != null) {
1172                if (ArrayUtils.contains(saveInfo.getOptionalIds(), id)
1173                        || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) {
1174                    return false;
1175                }
1176            }
1177
1178            final List<Dataset> datasets = response.getDatasets();
1179            if (datasets != null) {
1180                final int numDatasets = datasets.size();
1181
1182                for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) {
1183                    final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds();
1184
1185                    if (fields != null && fields.contains(id)) {
1186                        return false;
1187                    }
1188                }
1189            }
1190
1191            if (ArrayUtils.contains(response.getAuthenticationIds(), id)) {
1192                return false;
1193            }
1194        }
1195
1196        return true;
1197    }
1198
1199    void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
1200            int flags) {
1201        if (mDestroyed) {
1202            Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
1203                    + id + " destroyed");
1204            return;
1205        }
1206        if (sVerbose) {
1207            Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + action + ", flags=" + flags);
1208        }
1209        ViewState viewState = mViewStates.get(id);
1210
1211        if (viewState == null) {
1212            if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED
1213                    || action == ACTION_VIEW_ENTERED) {
1214                if (sVerbose) Slog.v(TAG, "Creating viewState for " + id + " on " + action);
1215                boolean isIgnored = isIgnoredLocked(id);
1216                viewState = new ViewState(this, id, this,
1217                        isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
1218                mViewStates.put(id, viewState);
1219                if (isIgnored) {
1220                    if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + id);
1221                    return;
1222                }
1223            } else {
1224                if (sVerbose) Slog.v(TAG, "Ignored action " + action + " for " + id);
1225                return;
1226            }
1227        }
1228
1229        switch(action) {
1230            case ACTION_START_SESSION:
1231                // View is triggering autofill.
1232                mCurrentViewId = viewState.id;
1233                viewState.update(value, virtualBounds, flags);
1234                viewState.setState(ViewState.STATE_STARTED_SESSION);
1235                requestNewFillResponseLocked(flags);
1236                break;
1237            case ACTION_VALUE_CHANGED:
1238                if (value != null && !value.equals(viewState.getCurrentValue())) {
1239                    // Always update the internal state.
1240                    viewState.setCurrentValue(value);
1241
1242                    // Must check if this update was caused by autofilling the view, in which
1243                    // case we just update the value, but not the UI.
1244                    final AutofillValue filledValue = viewState.getAutofilledValue();
1245                    if (value.equals(filledValue)) {
1246                        return;
1247                    }
1248                    // Update the internal state...
1249                    viewState.setState(ViewState.STATE_CHANGED);
1250
1251                    //..and the UI
1252                    if (value.isText()) {
1253                        getUiForShowing().filterFillUi(value.getTextValue().toString(), this);
1254                    } else {
1255                        getUiForShowing().filterFillUi(null, this);
1256                    }
1257                }
1258                break;
1259            case ACTION_VIEW_ENTERED:
1260                if (sVerbose && virtualBounds != null) {
1261                    Slog.w(TAG, "entered on virtual child " + id + ": " + virtualBounds);
1262                }
1263                requestNewFillResponseIfNecessaryLocked(id, viewState, flags);
1264
1265                // Remove the UI if the ViewState has changed.
1266                if (mCurrentViewId != viewState.id) {
1267                    mUi.hideFillUi(this);
1268                    mCurrentViewId = viewState.id;
1269                }
1270
1271                // If the ViewState is ready to be displayed, onReady() will be called.
1272                viewState.update(value, virtualBounds, flags);
1273                break;
1274            case ACTION_VIEW_EXITED:
1275                if (mCurrentViewId == viewState.id) {
1276                    if (sVerbose) Slog.d(TAG, "Exiting view " + id);
1277                    mUi.hideFillUi(this);
1278                    mCurrentViewId = null;
1279                }
1280                break;
1281            default:
1282                Slog.w(TAG, "updateLocked(): unknown action: " + action);
1283        }
1284    }
1285
1286    /**
1287     * Checks whether a view should be ignored.
1288     */
1289    private boolean isIgnoredLocked(AutofillId id) {
1290        if (mResponses == null || mResponses.size() == 0) {
1291            return false;
1292        }
1293        // Always check the latest response only
1294        final FillResponse response = mResponses.valueAt(mResponses.size() - 1);
1295        return ArrayUtils.contains(response.getIgnoredIds(), id);
1296    }
1297
1298    @Override
1299    public void onFillReady(FillResponse response, AutofillId filledId,
1300            @Nullable AutofillValue value) {
1301        synchronized (mLock) {
1302            if (mDestroyed) {
1303                Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
1304                        + id + " destroyed");
1305                return;
1306            }
1307        }
1308
1309        String filterText = null;
1310        if (value != null && value.isText()) {
1311            filterText = value.getTextValue().toString();
1312        }
1313
1314        getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this);
1315    }
1316
1317    boolean isDestroyed() {
1318        synchronized (mLock) {
1319            return mDestroyed;
1320        }
1321    }
1322
1323    IAutoFillManagerClient getClient() {
1324        synchronized (mLock) {
1325            return mClient;
1326        }
1327    }
1328
1329    private void notifyUnavailableToClient() {
1330        synchronized (mLock) {
1331            if (!mHasCallback || mCurrentViewId == null) return;
1332            try {
1333                mClient.notifyNoFillUi(id, mCurrentViewId);
1334            } catch (RemoteException e) {
1335                Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
1336            }
1337        }
1338    }
1339
1340    private void updateTrackedIdsLocked() {
1341        if (mResponses == null || mResponses.size() == 0) {
1342            return;
1343        }
1344
1345        // Only track the views of the last response as only those are reported back to the
1346        // service, see #showSaveLocked
1347        final FillResponse response = mResponses.valueAt(getLastResponseIndexLocked());
1348
1349        ArraySet<AutofillId> trackedViews = null;
1350        boolean saveOnAllViewsInvisible = false;
1351        final SaveInfo saveInfo = response.getSaveInfo();
1352        if (saveInfo != null) {
1353            saveOnAllViewsInvisible =
1354                    (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
1355
1356            // We only need to track views if we want to save once they become invisible.
1357            if (saveOnAllViewsInvisible) {
1358                if (trackedViews == null) {
1359                    trackedViews = new ArraySet<>();
1360                }
1361                if (saveInfo.getRequiredIds() != null) {
1362                    Collections.addAll(trackedViews, saveInfo.getRequiredIds());
1363                }
1364
1365                if (saveInfo.getOptionalIds() != null) {
1366                    Collections.addAll(trackedViews, saveInfo.getOptionalIds());
1367                }
1368            }
1369        }
1370
1371        // Must also track that are part of datasets, otherwise the FillUI won't be hidden when
1372        // they go away (if they're not savable).
1373
1374        final List<Dataset> datasets = response.getDatasets();
1375        ArraySet<AutofillId> fillableIds = null;
1376        if (datasets != null) {
1377            for (int i = 0; i < datasets.size(); i++) {
1378                final Dataset dataset = datasets.get(i);
1379                final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
1380                if (fieldIds == null) continue;
1381
1382                for (int j = 0; j < fieldIds.size(); j++) {
1383                    final AutofillId id = fieldIds.get(j);
1384                    if (trackedViews == null || !trackedViews.contains(id)) {
1385                        fillableIds = ArrayUtils.add(fillableIds, id);
1386                    }
1387                }
1388            }
1389        }
1390
1391        try {
1392            if (sVerbose) {
1393                Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds);
1394            }
1395            mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
1396                    toArray(fillableIds));
1397        } catch (RemoteException e) {
1398            Slog.w(TAG, "Cannot set tracked ids", e);
1399        }
1400    }
1401
1402    private void replaceResponseLocked(@NonNull FillResponse oldResponse,
1403            @NonNull FillResponse newResponse) {
1404        // Disassociate view states with the old response
1405        setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
1406        // Move over the id
1407        newResponse.setRequestId(oldResponse.getRequestId());
1408        // Replace the old response
1409        mResponses.put(newResponse.getRequestId(), newResponse);
1410        // Now process the new response
1411        processResponseLocked(newResponse, 0);
1412    }
1413
1414    private void processNullResponseLocked(int flags) {
1415        if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null");
1416        if ((flags & FLAG_MANUAL_REQUEST) != 0) {
1417            getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
1418        }
1419        mService.resetLastResponse();
1420        // Nothing to be done, but need to notify client.
1421        notifyUnavailableToClient();
1422        removeSelf();
1423    }
1424
1425    private void processResponseLocked(@NonNull FillResponse newResponse, int flags) {
1426        // Make sure we are hiding the UI which will be shown
1427        // only if handling the current response requires it.
1428        mUi.hideAll(this);
1429
1430        final int requestId = newResponse.getRequestId();
1431        if (sVerbose) {
1432            Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
1433                    + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse);
1434        }
1435
1436        if (mResponses == null) {
1437            mResponses = new SparseArray<>(4);
1438        }
1439        mResponses.put(requestId, newResponse);
1440        mClientState = newResponse.getClientState();
1441
1442        setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
1443        updateTrackedIdsLocked();
1444
1445        if (mCurrentViewId == null) {
1446            return;
1447        }
1448
1449        // Updates the UI, if necessary.
1450        final ViewState currentView = mViewStates.get(mCurrentViewId);
1451        currentView.maybeCallOnFillReady(flags);
1452    }
1453
1454    /**
1455     * Sets the state of all views in the given response.
1456     */
1457    private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
1458        final List<Dataset> datasets = response.getDatasets();
1459        if (datasets != null) {
1460            for (int i = 0; i < datasets.size(); i++) {
1461                final Dataset dataset = datasets.get(i);
1462                if (dataset == null) {
1463                    Slog.w(TAG, "Ignoring null dataset on " + datasets);
1464                    continue;
1465                }
1466                setViewStatesLocked(response, dataset, state, clearResponse);
1467            }
1468        } else if (response.getAuthentication() != null) {
1469            for (AutofillId autofillId : response.getAuthenticationIds()) {
1470                final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null);
1471                if (!clearResponse) {
1472                    viewState.setResponse(response);
1473                } else {
1474                    viewState.setResponse(null);
1475                }
1476            }
1477        }
1478        final SaveInfo saveInfo = response.getSaveInfo();
1479        if (saveInfo != null) {
1480            final AutofillId[] requiredIds = saveInfo.getRequiredIds();
1481            if (requiredIds != null) {
1482                for (AutofillId id : requiredIds) {
1483                    createOrUpdateViewStateLocked(id, state, null);
1484                }
1485            }
1486            final AutofillId[] optionalIds = saveInfo.getOptionalIds();
1487            if (optionalIds != null) {
1488                for (AutofillId id : optionalIds) {
1489                    createOrUpdateViewStateLocked(id, state, null);
1490                }
1491            }
1492        }
1493
1494        final AutofillId[] authIds = response.getAuthenticationIds();
1495        if (authIds != null) {
1496            for (AutofillId id : authIds) {
1497                createOrUpdateViewStateLocked(id, state, null);
1498            }
1499        }
1500    }
1501
1502    /**
1503     * Sets the state of all views in the given dataset and response.
1504     */
1505    private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
1506            int state, boolean clearResponse) {
1507        final ArrayList<AutofillId> ids = dataset.getFieldIds();
1508        final ArrayList<AutofillValue> values = dataset.getFieldValues();
1509        for (int j = 0; j < ids.size(); j++) {
1510            final AutofillId id = ids.get(j);
1511            final AutofillValue value = values.get(j);
1512            final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
1513            if (response != null) {
1514                viewState.setResponse(response);
1515            } else if (clearResponse) {
1516                viewState.setResponse(null);
1517            }
1518        }
1519    }
1520
1521    private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
1522            @Nullable AutofillValue value) {
1523        ViewState viewState = mViewStates.get(id);
1524        if (viewState != null)  {
1525            viewState.setState(state);
1526        } else {
1527            viewState = new ViewState(this, id, this, state);
1528            if (sVerbose) {
1529                Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state);
1530            }
1531            mViewStates.put(id, viewState);
1532        }
1533        if ((state & ViewState.STATE_AUTOFILLED) != 0) {
1534            viewState.setAutofilledValue(value);
1535        }
1536        return viewState;
1537    }
1538
1539    void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
1540        synchronized (mLock) {
1541            if (mDestroyed) {
1542                Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
1543                        + id + " destroyed");
1544                return;
1545            }
1546            // Autofill it directly...
1547            if (dataset.getAuthentication() == null) {
1548                if (generateEvent) {
1549                    mService.logDatasetSelected(dataset.getId(), id);
1550                }
1551
1552                autoFillApp(dataset);
1553                return;
1554            }
1555
1556            // ...or handle authentication.
1557            mService.logDatasetAuthenticationSelected(dataset.getId(), id);
1558            setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
1559            final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
1560
1561            final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
1562                    datasetIndex);
1563            startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
1564        }
1565    }
1566
1567    CharSequence getServiceName() {
1568        synchronized (mLock) {
1569            return mService.getServiceName();
1570        }
1571    }
1572
1573    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
1574        final Intent fillInIntent = new Intent();
1575
1576        final FillContext context = getFillContextByRequestIdLocked(requestId);
1577        if (context == null) {
1578            // TODO(b/653742740): this will crash system_server. We need to handle it, but we're
1579            // keeping it crashing for now so we can diagnose when it happens again
1580            Slog.wtf(TAG, "no FillContext for requestId" + requestId + "; mContexts= " + mContexts);
1581        }
1582        fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
1583        fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
1584        return fillInIntent;
1585    }
1586
1587    private void startAuthentication(int authenticationId, IntentSender intent,
1588            Intent fillInIntent) {
1589        try {
1590            synchronized (mLock) {
1591                mClient.authenticate(id, authenticationId, intent, fillInIntent);
1592            }
1593        } catch (RemoteException e) {
1594            Slog.e(TAG, "Error launching auth intent", e);
1595        }
1596    }
1597
1598    @Override
1599    public String toString() {
1600        return "Session: [id=" + id + ", pkg=" + mPackageName + "]";
1601    }
1602
1603    void dumpLocked(String prefix, PrintWriter pw) {
1604        final String prefix2 = prefix + "  ";
1605        pw.print(prefix); pw.print("id: "); pw.println(id);
1606        pw.print(prefix); pw.print("uid: "); pw.println(uid);
1607        pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
1608        pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
1609        pw.print(prefix); pw.print("mResponses: ");
1610        if (mResponses == null) {
1611            pw.println("null");
1612        } else {
1613            pw.println(mResponses.size());
1614            for (int i = 0; i < mResponses.size(); i++) {
1615                pw.print(prefix2); pw.print('#'); pw.print(i);
1616                pw.print(' '); pw.println(mResponses.valueAt(i));
1617            }
1618        }
1619        pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
1620        pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
1621        pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
1622        pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving);
1623        pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi);
1624        for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
1625            pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
1626            entry.getValue().dump(prefix2, pw);
1627        }
1628
1629        pw.print(prefix); pw.print("mContexts: " );
1630        if (mContexts != null) {
1631            int numContexts = mContexts.size();
1632            for (int i = 0; i < numContexts; i++) {
1633                FillContext context = mContexts.get(i);
1634
1635                pw.print(prefix2); pw.print(context);
1636                if (sVerbose) {
1637                    pw.println(context.getStructure() + " (look at logcat)");
1638
1639                    // TODO: add method on AssistStructure to dump on pw
1640                    context.getStructure().dump(false);
1641                }
1642            }
1643        } else {
1644            pw.println("null");
1645        }
1646
1647        pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
1648        pw.print(prefix); pw.print("mClientState: "); pw.println(
1649                Helper.bundleToString(mClientState));
1650        mRemoteFillService.dump(prefix, pw);
1651    }
1652
1653    void autoFillApp(Dataset dataset) {
1654        synchronized (mLock) {
1655            if (mDestroyed) {
1656                Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: "
1657                        + id + " destroyed");
1658                return;
1659            }
1660            try {
1661                // Skip null values as a null values means no change
1662                final int entryCount = dataset.getFieldIds().size();
1663                final List<AutofillId> ids = new ArrayList<>(entryCount);
1664                final List<AutofillValue> values = new ArrayList<>(entryCount);
1665                boolean waitingDatasetAuth = false;
1666                for (int i = 0; i < entryCount; i++) {
1667                    if (dataset.getFieldValues().get(i) == null) {
1668                        continue;
1669                    }
1670                    final AutofillId viewId = dataset.getFieldIds().get(i);
1671                    ids.add(viewId);
1672                    values.add(dataset.getFieldValues().get(i));
1673                    final ViewState viewState = mViewStates.get(viewId);
1674                    if (viewState != null
1675                            && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) {
1676                        if (sVerbose) {
1677                            Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth");
1678                        }
1679                        waitingDatasetAuth = true;
1680                        viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH);
1681                    }
1682                }
1683                if (!ids.isEmpty()) {
1684                    if (waitingDatasetAuth) {
1685                        mUi.hideFillUi(this);
1686                    }
1687                    if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
1688
1689                    mClient.autofill(id, ids, values);
1690                    setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false);
1691                }
1692            } catch (RemoteException e) {
1693                Slog.w(TAG, "Error autofilling activity: " + e);
1694            }
1695        }
1696    }
1697
1698    private AutoFillUI getUiForShowing() {
1699        synchronized (mLock) {
1700            mUi.setCallback(this);
1701            return mUi;
1702        }
1703    }
1704
1705    /**
1706     * Cleans up this session.
1707     *
1708     * <p>Typically called in 2 scenarios:
1709     *
1710     * <ul>
1711     *   <li>When the session naturally finishes (i.e., from {@link #removeSelfLocked()}.
1712     *   <li>When the service hosting the session is finished (for example, because the user
1713     *       disabled it).
1714     * </ul>
1715     */
1716    RemoteFillService destroyLocked() {
1717        if (mDestroyed) {
1718            return null;
1719        }
1720        mUi.destroyAll(mPendingSaveUi, this);
1721        mUi.clearCallback(this);
1722        mDestroyed = true;
1723        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
1724        return mRemoteFillService;
1725    }
1726
1727    /**
1728     * Cleans up this session and remove it from the service always, even if it does have a pending
1729     * Save UI.
1730     */
1731    void forceRemoveSelfLocked() {
1732        if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
1733
1734        mPendingSaveUi = null;
1735        removeSelfLocked();
1736        mUi.destroyAll(mPendingSaveUi, this);
1737    }
1738
1739    /**
1740     * Thread-safe version of {@link #removeSelfLocked()}.
1741     */
1742    private void removeSelf() {
1743        synchronized (mLock) {
1744            removeSelfLocked();
1745        }
1746    }
1747
1748    /**
1749     * Cleans up this session and remove it from the service, but but only if it does not have a
1750     * pending Save UI.
1751     */
1752    void removeSelfLocked() {
1753        if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
1754        if (mDestroyed) {
1755            Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: "
1756                    + id + " destroyed");
1757            return;
1758        }
1759        if (isSaveUiPending()) {
1760            Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
1761            return;
1762        }
1763
1764        final RemoteFillService remoteFillService = destroyLocked();
1765        mService.removeSessionLocked(id);
1766        if (remoteFillService != null) {
1767            remoteFillService.destroy();
1768        }
1769    }
1770
1771    void onPendingSaveUi(int operation, @NonNull IBinder token) {
1772        getUiForShowing().onPendingSaveUi(operation, token);
1773    }
1774
1775    /**
1776     * Checks whether this session is hiding the Save UI to handle a custom description link for
1777     * a specific {@code token} created by {@link PendingUi#PendingUi(IBinder)}.
1778     */
1779    boolean isSaveUiPendingForToken(@NonNull IBinder token) {
1780        return isSaveUiPending() && token.equals(mPendingSaveUi.getToken());
1781    }
1782
1783    /**
1784     * Checks whether this session is hiding the Save UI to handle a custom description link.
1785     */
1786    private boolean isSaveUiPending() {
1787        return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
1788    }
1789
1790    private int getLastResponseIndexLocked() {
1791        // The response ids are monotonically increasing so
1792        // we just find the largest id which is the last. We
1793        // do not rely on the internal ordering in sparse
1794        // array to avoid - wow this stopped working!?
1795        int lastResponseIdx = -1;
1796        int lastResponseId = -1;
1797        if (mResponses != null) {
1798            final int responseCount = mResponses.size();
1799            for (int i = 0; i < responseCount; i++) {
1800                if (mResponses.keyAt(i) > lastResponseId) {
1801                    lastResponseIdx = i;
1802                }
1803            }
1804        }
1805        return lastResponseIdx;
1806    }
1807}
1808