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