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