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