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