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