Session.java revision f43ca7968fed9fe8862d9b7217c94687dbb12e0a
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
17
18package com.android.server.autofill;
19
20import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
21import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
22import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
23import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED;
24import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
25
26import static com.android.server.autofill.Helper.DEBUG;
27import static com.android.server.autofill.Helper.VERBOSE;
28
29import android.annotation.NonNull;
30import android.annotation.Nullable;
31import android.app.assist.AssistStructure;
32import android.app.assist.AssistStructure.ViewNode;
33import android.app.assist.AssistStructure.WindowNode;
34import android.content.ComponentName;
35import android.content.Context;
36import android.content.Intent;
37import android.content.IntentSender;
38import android.graphics.Rect;
39import android.metrics.LogMaker;
40import android.os.Binder;
41import android.os.Bundle;
42import android.os.IBinder;
43import android.os.Parcelable;
44import android.os.RemoteException;
45import android.provider.Settings;
46import android.service.autofill.AutofillService;
47import android.service.autofill.Dataset;
48import android.service.autofill.FillResponse;
49import android.service.autofill.SaveInfo;
50import android.util.ArrayMap;
51import android.util.DebugUtils;
52import android.util.Slog;
53import android.view.autofill.AutofillId;
54import android.view.autofill.AutofillManager;
55import android.view.autofill.AutofillValue;
56import android.view.autofill.IAutoFillManagerClient;
57import android.view.autofill.IAutofillWindowPresenter;
58
59import com.android.internal.annotations.GuardedBy;
60import com.android.internal.logging.MetricsLogger;
61import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
62import com.android.internal.os.HandlerCaller;
63import com.android.server.autofill.ui.AutoFillUI;
64
65import java.io.PrintWriter;
66import java.util.ArrayList;
67import java.util.Map;
68import java.util.Map.Entry;
69
70/**
71 * A session for a given activity.
72 *
73 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
74 * of the current {@link ViewState} to display the appropriate UI.
75 *
76 * <p>Although the autofill requests and callbacks are stateless from the service's point of
77 * view, we need to keep state in the framework side for cases such as authentication. For
78 * example, when service return a {@link FillResponse} that contains all the fields needed
79 * to fill the activity but it requires authentication first, that response need to be held
80 * until the user authenticates or it times out.
81 */
82// TODO(b/33197203): make sure sessions are removed (and tested by CTS):
83// - On all authentication scenarios.
84// - When user does not interact back after a while.
85// - When service is unbound.
86final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
87        AutoFillUI.AutoFillUiCallback {
88    private static final String TAG = "AutofillSession";
89
90    private final AutofillManagerServiceImpl mService;
91    private final IBinder mActivityToken;
92    private final IBinder mWindowToken;
93    private final HandlerCaller mHandlerCaller;
94    private final Object mLock;
95    private final AutoFillUI mUi;
96
97    private final MetricsLogger mMetricsLogger = new MetricsLogger();
98
99    /** Package name of the app that is auto-filled */
100    @NonNull private final String mPackageName;
101
102    @GuardedBy("mLock")
103    private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
104
105    /**
106     * Id of the View currently being displayed.
107     */
108    @GuardedBy("mLock")
109    @Nullable private AutofillId mCurrentViewId;
110
111    private final IAutoFillManagerClient mClient;
112
113    @GuardedBy("mLock")
114    RemoteFillService mRemoteFillService;
115
116    @GuardedBy("mLock")
117    private ArrayList<FillResponse> mResponses;
118
119    /**
120     * Response that requires a service authentitcation request.
121     */
122    @GuardedBy("mLock")
123    private FillResponse mResponseWaitingAuth;
124
125    /**
126     * Dataset that when tapped launched a service authentication request.
127     */
128    @GuardedBy("mLock")
129    private Dataset mDatasetWaitingAuth;
130
131    /**
132     * Assist structure sent by the app; it will be updated (sanitized, change values for save)
133     * before sent to {@link AutofillService}.
134     */
135    @GuardedBy("mLock") AssistStructure mStructure;
136
137    /**
138     * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
139     */
140    private boolean mHasCallback;
141
142    /**
143     * Flags used to start the session.
144     */
145    int mFlags;
146
147    Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
148            @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
149            @NonNull Object lock, @NonNull IBinder activityToken,
150            @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
151            int flags, @NonNull ComponentName componentName, @NonNull String packageName) {
152        mService = service;
153        mLock = lock;
154        mUi = ui;
155        mHandlerCaller = handlerCaller;
156        mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
157        mActivityToken = activityToken;
158        mWindowToken = windowToken;
159        mHasCallback = hasCallback;
160        mPackageName = packageName;
161        mFlags = flags;
162
163        mClient = IAutoFillManagerClient.Stub.asInterface(client);
164        try {
165            client.linkToDeath(() -> {
166                if (VERBOSE) {
167                    Slog.v(TAG, "app binder died");
168                }
169
170                removeSelf();
171            }, 0);
172        } catch (RemoteException e) {
173            Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
174        }
175
176        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
177    }
178
179    // FillServiceCallbacks
180    @Override
181    public void onFillRequestSuccess(@Nullable FillResponse response,
182            @NonNull String servicePackageName) {
183        if (response == null) {
184            // Nothing to be done, but need to notify client.
185            notifyUnavailableToClient();
186            removeSelf();
187            return;
188        }
189
190        if ((response.getDatasets() == null || response.getDatasets().isEmpty())
191                        && response.getAuthentication() == null) {
192            // Response is "empty" from an UI point of view, need to notify client.
193            notifyUnavailableToClient();
194        }
195        synchronized (mLock) {
196            if (response.getAuthentication() != null) {
197                // TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
198                mResponseWaitingAuth = response;
199            }
200            processResponseLocked(response);
201        }
202
203        final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
204                .setType(MetricsEvent.TYPE_SUCCESS)
205                .setPackageName(mPackageName)
206                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
207                        response.getDatasets() == null ? 0 : response.getDatasets().size())
208                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
209                        servicePackageName);
210        mMetricsLogger.write(log);
211    }
212
213    // FillServiceCallbacks
214    @Override
215    public void onFillRequestFailure(@Nullable CharSequence message,
216            @NonNull String servicePackageName) {
217        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
218                .setType(MetricsEvent.TYPE_FAILURE)
219                .setPackageName(mPackageName)
220                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
221        mMetricsLogger.write(log);
222
223        getUiForShowing().showError(message);
224        removeSelf();
225    }
226
227    // FillServiceCallbacks
228    @Override
229    public void onSaveRequestSuccess(@NonNull String servicePackageName) {
230        LogMaker log = (new LogMaker(
231                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
232                .setType(MetricsEvent.TYPE_SUCCESS)
233                .setPackageName(mPackageName)
234                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
235        mMetricsLogger.write(log);
236
237        // Nothing left to do...
238        removeSelf();
239    }
240
241    // FillServiceCallbacks
242    @Override
243    public void onSaveRequestFailure(@Nullable CharSequence message,
244            @NonNull String servicePackageName) {
245        LogMaker log = (new LogMaker(
246                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
247                .setType(MetricsEvent.TYPE_FAILURE)
248                .setPackageName(mPackageName)
249                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
250        mMetricsLogger.write(log);
251
252        getUiForShowing().showError(message);
253        removeSelf();
254    }
255
256    // FillServiceCallbacks
257    @Override
258    public void authenticate(IntentSender intent, Bundle extras) {
259        final Intent fillInIntent;
260        synchronized (mLock) {
261            fillInIntent = createAuthFillInIntent(mStructure, extras);
262        }
263        mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
264    }
265
266    // FillServiceCallbacks
267    @Override
268    public void onDisableSelf() {
269        mService.disableSelf();
270        synchronized (mLock) {
271            removeSelfLocked();
272        }
273    }
274
275    // FillServiceCallbacks
276    @Override
277    public void onServiceDied(RemoteFillService service) {
278        // TODO(b/33197203): implement
279    }
280
281    // AutoFillUiCallback
282    @Override
283    public void fill(Dataset dataset) {
284        mHandlerCaller.getHandler().post(() -> autoFill(dataset));
285    }
286
287    // AutoFillUiCallback
288    @Override
289    public void save() {
290        mHandlerCaller.getHandler()
291                .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, mActivityToken)
292                .sendToTarget();
293    }
294
295    // AutoFillUiCallback
296    @Override
297    public void cancelSave() {
298        mHandlerCaller.getHandler().post(() -> removeSelf());
299    }
300
301    // AutoFillUiCallback
302    @Override
303    public void requestShowFillUi(AutofillId id, int width, int height,
304            IAutofillWindowPresenter presenter) {
305        try {
306            final ViewState currentView = mViewStates.get(mCurrentViewId);
307            mClient.requestShowFillUi(mWindowToken, id, width, height,
308                    currentView.getVirtualBounds(), presenter);
309        } catch (RemoteException e) {
310            Slog.e(TAG, "Error requesting to show fill UI", e);
311        }
312    }
313
314    // AutoFillUiCallback
315    @Override
316    public void requestHideFillUi(AutofillId id) {
317        try {
318            mClient.requestHideFillUi(mWindowToken, id);
319        } catch (RemoteException e) {
320            Slog.e(TAG, "Error requesting to hide fill UI", e);
321        }
322    }
323
324    public void setAuthenticationResultLocked(Bundle data) {
325        if ((mResponseWaitingAuth == null && mDatasetWaitingAuth == null) || data == null) {
326            removeSelf();
327        } else {
328            final Parcelable result = data.getParcelable(
329                    AutofillManager.EXTRA_AUTHENTICATION_RESULT);
330            if (result instanceof FillResponse) {
331                mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
332                mResponseWaitingAuth = null;
333                processResponseLocked((FillResponse) result);
334            } else if (result instanceof Dataset) {
335                final Dataset dataset = (Dataset) result;
336                for (int i = 0; i < mResponses.size(); i++) {
337                    final FillResponse response = mResponses.get(i);
338                    final int index = response.getDatasets().indexOf(mDatasetWaitingAuth);
339                    if (index >= 0) {
340                        response.getDatasets().set(index, dataset);
341                        mDatasetWaitingAuth = null;
342                        autoFill(dataset);
343                        resetViewStatesLocked(dataset, ViewState.STATE_WAITING_DATASET_AUTH);
344                        return;
345                    }
346                }
347            }
348        }
349    }
350
351    public void setHasCallback(boolean hasIt) {
352        mHasCallback = hasIt;
353    }
354
355    /**
356     * Shows the save UI, when session can be saved.
357     *
358     * @return {@code true} if session is done, or {@code false} if it's pending user action.
359     */
360    public boolean showSaveLocked() {
361        if (mStructure == null) {
362            Slog.wtf(TAG, "showSaveLocked(): no mStructure");
363            return true;
364        }
365        if (mResponses == null) {
366            // Happens when the activity / session was finished before the service replied, or
367            // when the service cannot autofill it (and returned a null response).
368            if (DEBUG) {
369                Slog.d(TAG, "showSaveLocked(): no responses on session");
370            }
371            return true;
372        }
373
374        // TODO(b/33197203 , b/35707731): must iterate over all responses
375        final FillResponse response = mResponses.get(0);
376
377        final SaveInfo saveInfo = response.getSaveInfo();
378        if (DEBUG) {
379            Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
380        }
381
382        /*
383         * The Save dialog is only shown if all conditions below are met:
384         *
385         * - saveInfo is not null
386         * - autofillValue of all required ids is not null
387         * - autofillValue of at least one id (required or optional) has changed.
388         */
389
390        if (saveInfo == null) {
391            return true;
392        }
393
394        final AutofillId[] requiredIds = saveInfo.getRequiredIds();
395        if (requiredIds == null || requiredIds.length == 0) {
396            Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
397            return true;
398        }
399
400        boolean allRequiredAreNotEmpty = true;
401        boolean atLeastOneChanged = false;
402        for (int i = 0; i < requiredIds.length; i++) {
403            final AutofillId id = requiredIds[i];
404            final ViewState viewState = mViewStates.get(id);
405            if (viewState == null) {
406                Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
407                allRequiredAreNotEmpty = false;
408                break;
409            }
410
411            final AutofillValue currentValue = viewState.getCurrentValue();
412            if (currentValue == null || currentValue.isEmpty()) {
413                if (DEBUG) {
414                    Slog.d(TAG, "showSaveLocked(): empty value for required " + id );
415                }
416                allRequiredAreNotEmpty = false;
417                break;
418            }
419            final AutofillValue filledValue = viewState.getAutofilledValue();
420
421            if (!currentValue.equals(filledValue)) {
422                if (DEBUG) {
423                    Slog.d(TAG, "showSaveLocked(): found a change on required " + id + ": "
424                            + filledValue + " => " + currentValue);
425                }
426                atLeastOneChanged = true;
427            }
428        }
429
430        final AutofillId[] optionalIds = saveInfo.getOptionalIds();
431        if (allRequiredAreNotEmpty) {
432            if (!atLeastOneChanged && optionalIds != null) {
433                // No change on required ids yet, look for changes on optional ids.
434                for (int i = 0; i < optionalIds.length; i++) {
435                    final AutofillId id = optionalIds[i];
436                    final ViewState viewState = mViewStates.get(id);
437                    if (viewState == null) {
438                        Slog.w(TAG, "showSaveLocked(): no ViewState for optional " + id);
439                        continue;
440                    }
441                    if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
442                        final AutofillValue currentValue = viewState.getCurrentValue();
443                        final AutofillValue filledValue = viewState.getAutofilledValue();
444                        if (currentValue != null && !currentValue.equals(filledValue)) {
445                            if (DEBUG) {
446                                Slog.d(TAG, "finishSessionLocked(): found a change on optional "
447                                        + id + ": " + filledValue + " => " + currentValue);
448                            }
449                            atLeastOneChanged = true;
450                            break;
451                        }
452                    }
453                }
454            }
455            if (atLeastOneChanged) {
456                getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName);
457                return false;
458            }
459        }
460        // Nothing changed...
461        if (DEBUG) {
462            Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
463                    + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
464                    + ", atLeastOneChanged=" + atLeastOneChanged);
465        }
466        return true;
467    }
468
469    /**
470     * Calls service when user requested save.
471     */
472    void callSaveLocked() {
473        if (DEBUG) {
474            Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
475        }
476
477        // TODO(b/33197203 , b/35707731): decide how to handle bundle in multiple partitions
478        final Bundle extras = mResponses != null ? mResponses.get(0).getExtras() : null;
479
480        for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
481            final AutofillValue value = entry.getValue().getCurrentValue();
482            if (value == null) {
483                if (VERBOSE) {
484                    Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
485                }
486                continue;
487            }
488            final AutofillId id = entry.getKey();
489            final ViewNode node = findViewNodeByIdLocked(id);
490            if (node == null) {
491                Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
492                continue;
493            }
494            if (VERBOSE) {
495                Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
496            }
497
498            node.updateAutofillValue(value);
499        }
500
501        // Sanitize structure before it's sent to service.
502        mStructure.sanitizeForParceling(false);
503
504        if (VERBOSE) {
505            Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
506            mStructure.dump();
507        }
508
509        mRemoteFillService.onSaveRequest(mStructure, extras);
510    }
511
512    void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
513        ViewState viewState = mViewStates.get(id);
514
515        if (viewState == null) {
516            if ((flags & (FLAG_START_SESSION | FLAG_VALUE_CHANGED)) != 0) {
517                if (DEBUG) {
518                    Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags));
519                }
520                viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
521                mViewStates.put(id, viewState);
522            } else if ((flags & FLAG_VIEW_ENTERED) != 0) {
523                viewState = startPartitionLocked(id);
524            } else {
525                if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id);
526                return;
527            }
528        }
529
530        if ((flags & FLAG_START_SESSION) != 0) {
531            // View is triggering autofill.
532            mCurrentViewId = viewState.id;
533            viewState.update(value, virtualBounds);
534            viewState.setState(ViewState.STATE_STARTED_SESSION);
535            return;
536        }
537
538        if ((flags & FLAG_VALUE_CHANGED) != 0) {
539            if (value != null && !value.equals(viewState.getCurrentValue())) {
540                // Always update the internal state.
541                viewState.setCurrentValue(value);
542
543                // Must check if this update was caused by autofilling the view, in which
544                // case we just update the value, but not the UI.
545                final AutofillValue filledValue = viewState.getAutofilledValue();
546                if (value.equals(filledValue)) {
547                    return;
548                }
549                // Update the internal state...
550                viewState.setState(ViewState.STATE_CHANGED);
551
552                //..and the UI
553                if (value.isText()) {
554                    getUiForShowing().filterFillUi(value.getTextValue().toString());
555                } else {
556                    getUiForShowing().filterFillUi(null);
557                }
558            }
559
560            return;
561        }
562
563        if ((flags & FLAG_VIEW_ENTERED) != 0) {
564            // Remove the UI if the ViewState has changed.
565            if (mCurrentViewId != viewState.id) {
566                mUi.hideFillUi(mCurrentViewId != null ? mCurrentViewId : null);
567                mCurrentViewId = viewState.id;
568            }
569
570            // If the ViewState is ready to be displayed, onReady() will be called.
571            viewState.update(value, virtualBounds);
572
573            return;
574        }
575
576        if ((flags & FLAG_VIEW_EXITED) != 0) {
577            if (mCurrentViewId == viewState.id) {
578                mUi.hideFillUi(viewState.id);
579                mCurrentViewId = null;
580            }
581            return;
582        }
583
584        Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags));
585    }
586
587    private ViewState startPartitionLocked(AutofillId id) {
588        if (DEBUG) {
589            Slog.d(TAG, "Starting partition for view id " + id);
590        }
591        final ViewState viewState =
592                new ViewState(this, id, this,ViewState.STATE_STARTED_PARTITION);
593        mViewStates.put(id, viewState);
594
595        /*
596         * TODO(b/33197203 , b/35707731): when start a new partition, it should
597         *
598         * - add autofilled fields as sanitized
599         * - set focus on ViewStructure that triggered it
600         * - pass the first onFillRequest() bundle
601         * - optional: perhaps add a new flag onFilLRequest() to indicate it's a new partition?
602         */
603        mRemoteFillService.onFillRequest(mStructure, null, 0);
604
605        return viewState;
606    }
607
608    @Override
609    public void onFillReady(FillResponse response, AutofillId filledId,
610            @Nullable AutofillValue value) {
611        String filterText = null;
612        if (value != null && value.isText()) {
613            filterText = value.getTextValue().toString();
614        }
615
616        getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
617    }
618
619    String getFlagAsString(int flag) {
620        return DebugUtils.flagsToString(AutofillManager.class, "FLAG_", flag);
621    }
622
623    private void notifyUnavailableToClient() {
624        if (mCurrentViewId == null) {
625            // TODO(b/33197203): temporary sanity check; should never happen
626            Slog.w(TAG, "notifyUnavailable(): mCurrentViewId is null");
627            return;
628        }
629        if (!mHasCallback) return;
630        try {
631            mClient.notifyNoFillUi(mWindowToken, mCurrentViewId);
632        } catch (RemoteException e) {
633            Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
634                    + " id=" + mCurrentViewId, e);
635        }
636    }
637
638    private void processResponseLocked(FillResponse response) {
639        if (DEBUG) {
640            Slog.d(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response);
641        }
642
643        if (mCurrentViewId == null) {
644            // TODO(b/33197203): temporary sanity check; should never happen
645            Slog.w(TAG, "processResponseLocked(): mCurrentViewId is null");
646            return;
647        }
648
649        if (mResponses == null) {
650            mResponses = new ArrayList<>(4);
651        }
652        mResponses.add(response);
653
654        setViewStatesLocked(response, ViewState.STATE_FILLABLE);
655
656        if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
657                && response.getDatasets().size() == 1) {
658            Slog.d(TAG, "autofilling manual request directly");
659            autoFill(response.getDatasets().get(0));
660            return;
661        }
662
663        // Updates the UI, if necessary.
664        final ViewState currentView = mViewStates.get(mCurrentViewId);
665        currentView.maybeCallOnFillReady();
666    }
667
668    /**
669     * Sets the state of all views in the given response.
670     */
671    private void setViewStatesLocked(FillResponse response, int state) {
672        final ArrayList<Dataset> datasets = response.getDatasets();
673        if (datasets != null) {
674            for (int i = 0; i < datasets.size(); i++) {
675                final Dataset dataset = datasets.get(i);
676                if (dataset == null) {
677                    Slog.w(TAG, "Ignoring null dataset on " + datasets);
678                    continue;
679                }
680                setViewStatesLocked(response, dataset, state);
681            }
682        }
683    }
684
685    /**
686     * Sets the state of all views in the given dataset and response.
687     */
688    private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
689            int state) {
690        final ArrayList<AutofillId> ids = dataset.getFieldIds();
691        final ArrayList<AutofillValue> values = dataset.getFieldValues();
692        for (int j = 0; j < ids.size(); j++) {
693            final AutofillId id = ids.get(j);
694            ViewState viewState = mViewStates.get(id);
695            if (viewState != null)  {
696                viewState.setState(state);
697            } else {
698                viewState = new ViewState(this, id, this, state);
699                if (DEBUG) { // TODO(b/33197203): change to VERBOSE once stable
700                    Slog.d(TAG, "Adding autofillable view with id " + id + " and state " + state);
701                }
702                mViewStates.put(id, viewState);
703            }
704            if ((state & ViewState.STATE_AUTOFILLED) != 0) {
705                viewState.setAutofilledValue(values.get(j));
706            }
707
708            if (response != null) {
709                viewState.setResponse(response);
710            }
711        }
712    }
713
714    /**
715     * Resets the given state from all existing views in the given dataset.
716     */
717    private void resetViewStatesLocked(@NonNull Dataset dataset, int state) {
718        final ArrayList<AutofillId> ids = dataset.getFieldIds();
719        for (int j = 0; j < ids.size(); j++) {
720            final AutofillId id = ids.get(j);
721            final ViewState viewState = mViewStates.get(id);
722            if (viewState != null)  {
723                viewState.resetState(state);
724            }
725        }
726    }
727
728    void autoFill(Dataset dataset) {
729        synchronized (mLock) {
730            // Autofill it directly...
731            if (dataset.getAuthentication() == null) {
732                autoFillApp(dataset);
733                return;
734            }
735
736            // ...or handle authentication.
737            // TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
738            mDatasetWaitingAuth = dataset;
739            setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH);
740            final Intent fillInIntent = createAuthFillInIntent(mStructure, null);
741            startAuthentication(dataset.getAuthentication(), fillInIntent);
742        }
743    }
744
745    CharSequence getServiceName() {
746        return mService.getServiceName();
747    }
748
749    FillResponse getResponseWaitingAuth() {
750        return mResponseWaitingAuth;
751    }
752
753    private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) {
754        final Intent fillInIntent = new Intent();
755        fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
756        if (extras != null) {
757            fillInIntent.putExtra(AutofillManager.EXTRA_DATA_EXTRAS, extras);
758        }
759        return fillInIntent;
760    }
761
762    private void startAuthentication(IntentSender intent, Intent fillInIntent) {
763        try {
764            mClient.authenticate(intent, fillInIntent);
765        } catch (RemoteException e) {
766            Slog.e(TAG, "Error launching auth intent", e);
767        }
768    }
769
770    void dumpLocked(String prefix, PrintWriter pw) {
771        pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
772        pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
773        pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses);
774        pw.print(prefix); pw.print("mResponseWaitingAuth: "); pw.println(mResponseWaitingAuth);
775        pw.print(prefix); pw.print("mDatasetWaitingAuth: "); pw.println(mDatasetWaitingAuth);
776        pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
777        pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
778        final String prefix2 = prefix + "  ";
779        for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
780            pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
781            entry.getValue().dump(prefix2, pw);
782        }
783        if (VERBOSE) {
784            pw.print(prefix); pw.print("mStructure: " );
785            // TODO(b/33197203): add method do dump AssistStructure on pw
786            if (mStructure != null) {
787                pw.println("look at logcat" );
788                mStructure.dump(); // dumps to logcat
789            } else {
790                pw.println("null");
791            }
792        }
793        pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
794        mRemoteFillService.dump(prefix, pw);
795    }
796
797    void autoFillApp(Dataset dataset) {
798        synchronized (mLock) {
799            try {
800                if (DEBUG) {
801                    Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
802                }
803                mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
804                setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED);
805            } catch (RemoteException e) {
806                Slog.w(TAG, "Error autofilling activity: " + e);
807            }
808        }
809    }
810
811    private AutoFillUI getUiForShowing() {
812        synchronized (mLock) {
813            mUi.setCallback(this);
814            return mUi;
815        }
816    }
817
818    private ViewNode findViewNodeByIdLocked(AutofillId id) {
819        final int size = mStructure.getWindowNodeCount();
820        for (int i = 0; i < size; i++) {
821            final WindowNode window = mStructure.getWindowNodeAt(i);
822            final ViewNode root = window.getRootViewNode();
823            if (id.equals(root.getAutofillId())) {
824                return root;
825            }
826            final ViewNode child = findViewNodeByIdLocked(root, id);
827            if (child != null) {
828                return child;
829            }
830        }
831        return null;
832    }
833
834    private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) {
835        final int childrenSize = parent.getChildCount();
836        if (childrenSize > 0) {
837            for (int i = 0; i < childrenSize; i++) {
838                final ViewNode child = parent.getChildAt(i);
839                if (id.equals(child.getAutofillId())) {
840                    return child;
841                }
842                final ViewNode grandChild = findViewNodeByIdLocked(child, id);
843                if (grandChild != null && id.equals(grandChild.getAutofillId())) {
844                    return grandChild;
845                }
846            }
847        }
848        return null;
849    }
850
851    void destroyLocked() {
852        mRemoteFillService.destroy();
853        mUi.setCallback(null);
854        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
855    }
856
857    void removeSelf() {
858        synchronized (mLock) {
859            removeSelfLocked();
860        }
861    }
862
863    void removeSelfLocked() {
864        if (VERBOSE) {
865            Slog.v(TAG, "removeSelfLocked()");
866        }
867        destroyLocked();
868        mService.removeSessionLocked(mActivityToken);
869    }
870}
871