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