AutofillManager.java revision fc513f98d7086605e9b3499d90a88d3b4592c2b6
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 android.view.autofill;
18
19import static android.view.autofill.Helper.DEBUG;
20import static android.view.autofill.Helper.VERBOSE;
21
22import android.annotation.IntDef;
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentSender;
28import android.graphics.Rect;
29import android.metrics.LogMaker;
30import android.os.Bundle;
31import android.os.IBinder;
32import android.os.Parcelable;
33import android.os.RemoteException;
34import android.service.autofill.AutofillService;
35import android.service.autofill.FillEventHistory;
36import android.util.ArrayMap;
37import android.util.ArraySet;
38import android.util.Log;
39import android.util.SparseArray;
40import android.view.View;
41import android.view.WindowManagerGlobal;
42
43import com.android.internal.annotations.GuardedBy;
44import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.nano.MetricsProto;
46
47import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
49import java.lang.ref.WeakReference;
50import java.util.List;
51import java.util.Objects;
52
53/**
54 * App entry point to the Autofill Framework.
55 *
56 * <p>It is safe to call into this from any thread.
57 */
58public final class AutofillManager {
59
60    private static final String TAG = "AutofillManager";
61
62    /**
63     * Intent extra: The assist structure which captures the filled screen.
64     *
65     * <p>
66     * Type: {@link android.app.assist.AssistStructure}
67     */
68    public static final String EXTRA_ASSIST_STRUCTURE =
69            "android.view.autofill.extra.ASSIST_STRUCTURE";
70
71    /**
72     * Intent extra: The result of an authentication operation. It is
73     * either a fully populated {@link android.service.autofill.FillResponse}
74     * or a fully populated {@link android.service.autofill.Dataset} if
75     * a response or a dataset is being authenticated respectively.
76     *
77     * <p>
78     * Type: {@link android.service.autofill.FillResponse} or a
79     * {@link android.service.autofill.Dataset}
80     */
81    public static final String EXTRA_AUTHENTICATION_RESULT =
82            "android.view.autofill.extra.AUTHENTICATION_RESULT";
83
84    /**
85     * Intent extra: The optional extras provided by the
86     * {@link android.service.autofill.AutofillService}.
87     *
88     * <p>For example, when the service responds to a {@link
89     * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
90     * a {@code FillResponse} that requires authentication, the Intent that launches the
91     * service authentication will contain the Bundle set by
92     * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
93     *
94     * <p>
95     * Type: {@link android.os.Bundle}
96     */
97    public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS";
98
99    static final String SESSION_ID_TAG = "android:sessionId";
100    static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
101
102    /**
103     * @deprecated Use {@link android.service.autofill.FillRequest#FLAG_MANUAL_REQUEST}
104     * @hide
105     */
106    // TODO(b/37563972): remove (and change value of private flags)
107    @Deprecated
108    public static final int FLAG_MANUAL_REQUEST = 0x1;
109
110    // TODO(b/37563972): start from 0x1 once FLAG_MANUAL_REQUEST is gone
111    /** @hide */ public static final int FLAG_START_SESSION = 0x80000000;
112    /** @hide */ public static final int FLAG_VIEW_ENTERED =  0x40000000;
113    /** @hide */ public static final int FLAG_VIEW_EXITED =   0x20000000;
114    /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000;
115
116    private final MetricsLogger mMetricsLogger = new MetricsLogger();
117
118    /**
119     * There is currently no session running.
120     * {@hide}
121     */
122    public static final int NO_SESSION = Integer.MIN_VALUE;
123
124    private final IAutoFillManager mService;
125
126    private final Object mLock = new Object();
127
128    @GuardedBy("mLock")
129    private IAutoFillManagerClient mServiceClient;
130
131    @GuardedBy("mLock")
132    private AutofillCallback mCallback;
133
134    private final Context mContext;
135
136    @GuardedBy("mLock")
137    private int mSessionId = NO_SESSION;
138
139    @GuardedBy("mLock")
140    private boolean mEnabled;
141
142    /** If a view changes to this mapping the autofill operation was successful */
143    @GuardedBy("mLock")
144    @Nullable private ParcelableMap mLastAutofilledData;
145
146    /** If view tracking is enabled, contains the tracking state */
147    @GuardedBy("mLock")
148    @Nullable private TrackedViews mTrackedViews;
149
150    /** @hide */
151    public interface AutofillClient {
152        /**
153         * Asks the client to start an authentication flow.
154         *
155         * @param intent The authentication intent.
156         * @param fillInIntent The authentication fill-in intent.
157         */
158        void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent);
159
160        /**
161         * Tells the client this manager has state to be reset.
162         */
163        void autofillCallbackResetableStateAvailable();
164
165        /**
166         * Request showing the autofill UI.
167         *
168         * @param anchor The real view the UI needs to anchor to.
169         * @param width The width of the fill UI content.
170         * @param height The height of the fill UI content.
171         * @param virtualBounds The bounds of the virtual decendant of the anchor.
172         * @param presenter The presenter that controls the fill UI window.
173         * @return Whether the UI was shown.
174         */
175        boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
176                @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
177
178        /**
179         * Request hiding the autofill UI.
180         *
181         * @return Whether the UI was hidden.
182         */
183        boolean autofillCallbackRequestHideFillUi();
184
185        /**
186         * Checks if the view is currently attached and visible.
187         *
188         * @return {@code true} iff the view is attached or visible
189         */
190        boolean getViewVisibility(int viewId);
191
192        /**
193         * Checks is the client is currently visible as understood by autofill.
194         *
195         * @return {@code true} if the client is currently visible
196         */
197        boolean isVisibleForAutofill();
198    }
199
200    /**
201     * @hide
202     */
203    public AutofillManager(Context context, IAutoFillManager service) {
204        mContext = context;
205        mService = service;
206    }
207
208    /**
209     * Restore state after activity lifecycle
210     *
211     * @param savedInstanceState The state to be restored
212     *
213     * {@hide}
214     */
215    public void onCreate(Bundle savedInstanceState) {
216        if (!hasAutofillFeature()) {
217            return;
218        }
219        synchronized (mLock) {
220            mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
221
222            if (mSessionId != NO_SESSION) {
223                Log.w(TAG, "New session was started before onCreate()");
224                return;
225            }
226
227            mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
228
229            if (mSessionId != NO_SESSION) {
230                ensureServiceClientAddedIfNeededLocked();
231
232                final AutofillClient client = getClientLocked();
233                if (client != null) {
234                    try {
235                        final boolean sessionWasRestored = mService.restoreSession(mSessionId,
236                                mContext.getActivityToken(), mServiceClient.asBinder());
237
238                        if (!sessionWasRestored) {
239                            Log.w(TAG, "Session " + mSessionId + " could not be restored");
240                            mSessionId = NO_SESSION;
241                        } else {
242                            if (DEBUG) {
243                                Log.d(TAG, "session " + mSessionId + " was restored");
244                            }
245
246                            client.autofillCallbackResetableStateAvailable();
247                        }
248                    } catch (RemoteException e) {
249                        Log.e(TAG, "Could not figure out if there was an autofill session", e);
250                    }
251                }
252            }
253        }
254    }
255
256    /**
257     * Set window future popup windows should be attached to.
258     *
259     * @param windowToken The window the popup windows should be attached to
260     *
261     * {@hide}
262     */
263    public void onAttachedToWindow(@NonNull IBinder windowToken) {
264        if (!hasAutofillFeature()) {
265            return;
266        }
267        synchronized (mLock) {
268            if (mSessionId == NO_SESSION) {
269                return;
270            }
271
272            try {
273                mService.setWindow(mSessionId, windowToken);
274            } catch (RemoteException e) {
275                Log.e(TAG, "Could not attach window to session " + mSessionId);
276            }
277        }
278    }
279
280    /**
281     * Called once the client becomes visible.
282     *
283     * @see AutofillClient#isVisibleForAutofill()
284     *
285     * {@hide}
286     */
287    public void onVisibleForAutofill() {
288        synchronized (mLock) {
289            if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
290                mTrackedViews.onVisibleForAutofill();
291            }
292        }
293    }
294
295    /**
296     * Save state before activity lifecycle
297     *
298     * @param outState Place to store the state
299     *
300     * {@hide}
301     */
302    public void onSaveInstanceState(Bundle outState) {
303        if (!hasAutofillFeature()) {
304            return;
305        }
306        synchronized (mLock) {
307            if (mSessionId != NO_SESSION) {
308                outState.putInt(SESSION_ID_TAG, mSessionId);
309            }
310
311            if (mLastAutofilledData != null) {
312                outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
313            }
314        }
315    }
316
317    /**
318     * Checks whether autofill is enabled for the current user.
319     *
320     * <p>Typically used to determine whether the option to explicitly request autofill should
321     * be offered - see {@link #requestAutofill(View)}.
322     *
323     * @return whether autofill is enabled for the current user.
324     */
325    public boolean isEnabled() {
326        if (!hasAutofillFeature()) {
327            return false;
328        }
329        synchronized (mLock) {
330            ensureServiceClientAddedIfNeededLocked();
331            return mEnabled;
332        }
333    }
334
335    /**
336     * Should always be called from {@link AutofillService#getFillEventHistory()}.
337     *
338     * @hide
339     */
340    @Nullable public FillEventHistory getFillEventHistory() {
341        try {
342            return mService.getFillEventHistory();
343        } catch (RemoteException e) {
344            e.rethrowFromSystemServer();
345            return null;
346        }
347    }
348
349    /**
350     * Explicitly requests a new autofill context.
351     *
352     * <p>Normally, the autofill context is automatically started when autofillable views are
353     * focused, but this method should be used in the cases where it must be explicitly requested,
354     * like a view that provides a contextual menu allowing users to autofill the activity.
355     *
356     * @param view view requesting the new autofill context.
357     */
358    public void requestAutofill(@NonNull View view) {
359        if (!hasAutofillFeature()) {
360            return;
361        }
362        synchronized (mLock) {
363            ensureServiceClientAddedIfNeededLocked();
364
365            if (!mEnabled) {
366                return;
367            }
368
369            final AutofillId id = getAutofillId(view);
370            final AutofillValue value = view.getAutofillValue();
371
372            startSessionLocked(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST);
373        }
374    }
375
376    /**
377     * Explicitly requests a new autofill context for virtual views.
378     *
379     * <p>Normally, the autofill context is automatically started when autofillable views are
380     * focused, but this method should be used in the cases where it must be explicitly requested,
381     * like a virtual view that provides a contextual menu allowing users to autofill the activity.
382     *
383     * @param view the {@link View} whose descendant is the virtual view.
384     * @param childId id identifying the virtual child inside the view.
385     * @param bounds child boundaries, relative to the top window.
386     */
387    public void requestAutofill(@NonNull View view, int childId, @NonNull Rect bounds) {
388        if (!hasAutofillFeature()) {
389            return;
390        }
391        synchronized (mLock) {
392            ensureServiceClientAddedIfNeededLocked();
393
394            if (!mEnabled) {
395                return;
396            }
397
398            final AutofillId id = getAutofillId(view, childId);
399            startSessionLocked(id, view.getWindowToken(), bounds, null, FLAG_MANUAL_REQUEST);
400        }
401    }
402
403
404    /**
405     * Called when a {@link View} that supports autofill is entered.
406     *
407     * @param view {@link View} that was entered.
408     */
409    public void notifyViewEntered(@NonNull View view) {
410        if (!hasAutofillFeature()) {
411            return;
412        }
413        AutofillCallback callback = null;
414        synchronized (mLock) {
415            ensureServiceClientAddedIfNeededLocked();
416
417            if (!mEnabled) {
418                if (mCallback != null) {
419                    callback = mCallback;
420                }
421            } else {
422                final AutofillId id = getAutofillId(view);
423                final AutofillValue value = view.getAutofillValue();
424
425                if (mSessionId == NO_SESSION) {
426                    // Starts new session.
427                    startSessionLocked(id, view.getWindowToken(), null, value, 0);
428                } else {
429                    // Update focus on existing session.
430                    updateSessionLocked(id, null, value, FLAG_VIEW_ENTERED);
431                }
432            }
433        }
434
435        if (callback != null) {
436            mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
437        }
438    }
439
440    /**
441     * Called when a {@link View} that supports autofill is exited.
442     *
443     * @param view {@link View} that was exited.
444     */
445    public void notifyViewExited(@NonNull View view) {
446        if (!hasAutofillFeature()) {
447            return;
448        }
449        synchronized (mLock) {
450            ensureServiceClientAddedIfNeededLocked();
451
452            if (mEnabled && mSessionId != NO_SESSION) {
453                final AutofillId id = getAutofillId(view);
454
455                // Update focus on existing session.
456                updateSessionLocked(id, null, null, FLAG_VIEW_EXITED);
457            }
458        }
459    }
460
461    /**
462     * Called when a {@link View view's} visibility changes.
463     *
464     * @param view {@link View} that was exited.
465     * @param isVisible visible if the view is visible in the view hierarchy.
466     *
467     * @hide
468     */
469    public void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
470        synchronized (mLock) {
471            if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
472                mTrackedViews.notifyViewVisibilityChange(view, isVisible);
473            }
474        }
475    }
476
477    /**
478     * Called when a virtual view that supports autofill is entered.
479     *
480     * @param view the {@link View} whose descendant is the virtual view.
481     * @param childId id identifying the virtual child inside the view.
482     * @param bounds child boundaries, relative to the top window.
483     */
484    public void notifyViewEntered(@NonNull View view, int childId, @NonNull Rect bounds) {
485        if (!hasAutofillFeature()) {
486            return;
487        }
488        AutofillCallback callback = null;
489        synchronized (mLock) {
490            ensureServiceClientAddedIfNeededLocked();
491
492            if (!mEnabled) {
493                if (mCallback != null) {
494                    callback = mCallback;
495                }
496            } else {
497                final AutofillId id = getAutofillId(view, childId);
498
499                if (mSessionId == NO_SESSION) {
500                    // Starts new session.
501                    startSessionLocked(id, view.getWindowToken(), bounds, null, 0);
502                } else {
503                    // Update focus on existing session.
504                    updateSessionLocked(id, bounds, null, FLAG_VIEW_ENTERED);
505                }
506            }
507        }
508
509        if (callback != null) {
510            callback.onAutofillEvent(view, childId,
511                    AutofillCallback.EVENT_INPUT_UNAVAILABLE);
512        }
513    }
514
515    /**
516     * Called when a virtual view that supports autofill is exited.
517     *
518     * @param view the {@link View} whose descendant is the virtual view.
519     * @param childId id identifying the virtual child inside the view.
520     */
521    public void notifyViewExited(@NonNull View view, int childId) {
522        if (!hasAutofillFeature()) {
523            return;
524        }
525        synchronized (mLock) {
526            ensureServiceClientAddedIfNeededLocked();
527
528            if (mEnabled && mSessionId != NO_SESSION) {
529                final AutofillId id = getAutofillId(view, childId);
530
531                // Update focus on existing session.
532                updateSessionLocked(id, null, null, FLAG_VIEW_EXITED);
533            }
534        }
535    }
536
537    /**
538     * Called to indicate the value of an autofillable {@link View} changed.
539     *
540     * @param view view whose value changed.
541     */
542    public void notifyValueChanged(View view) {
543        if (!hasAutofillFeature()) {
544            return;
545        }
546        AutofillId id = null;
547        boolean valueWasRead = false;
548        AutofillValue value = null;
549
550        synchronized (mLock) {
551            // If the session is gone some fields might still be highlighted, hence we have to
552            // remove the isAutofilled property even if no sessions are active.
553            if (mLastAutofilledData == null) {
554                view.setAutofilled(false);
555            } else {
556                id = getAutofillId(view);
557                if (mLastAutofilledData.containsKey(id)) {
558                    value = view.getAutofillValue();
559                    valueWasRead = true;
560
561                    if (Objects.equals(mLastAutofilledData.get(id), value)) {
562                        view.setAutofilled(true);
563                    } else {
564                        view.setAutofilled(false);
565                        mLastAutofilledData.remove(id);
566                    }
567                } else {
568                    view.setAutofilled(false);
569                }
570            }
571
572            if (!mEnabled || mSessionId == NO_SESSION) {
573                return;
574            }
575
576            if (id == null) {
577                id = getAutofillId(view);
578            }
579
580            if (!valueWasRead) {
581                value = view.getAutofillValue();
582            }
583
584            updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED);
585        }
586    }
587
588    /**
589     * Called to indicate the value of an autofillable virtual {@link View} changed.
590     *
591     * @param view the {@link View} whose descendant is the virtual view.
592     * @param childId id identifying the virtual child inside the parent view.
593     * @param value new value of the child.
594     */
595    public void notifyValueChanged(View view, int childId, AutofillValue value) {
596        if (!hasAutofillFeature()) {
597            return;
598        }
599        synchronized (mLock) {
600            if (!mEnabled || mSessionId == NO_SESSION) {
601                return;
602            }
603
604            final AutofillId id = getAutofillId(view, childId);
605            updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED);
606        }
607    }
608
609    /**
610     * Called to indicate the current autofill context should be commited.
611     *
612     * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
613     * call this method after the form is submitted and another page is rendered.
614     */
615    public void commit() {
616        if (!hasAutofillFeature()) {
617            return;
618        }
619        synchronized (mLock) {
620            if (!mEnabled && mSessionId == NO_SESSION) {
621                return;
622            }
623
624            finishSessionLocked();
625        }
626    }
627
628    /**
629     * Called to indicate the current autofill context should be cancelled.
630     *
631     * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
632     * call this method if the user does not post the form but moves to another form in this page.
633     */
634    public void cancel() {
635        if (!hasAutofillFeature()) {
636            return;
637        }
638        synchronized (mLock) {
639            if (!mEnabled && mSessionId == NO_SESSION) {
640                return;
641            }
642
643            cancelSessionLocked();
644        }
645    }
646
647    /**
648     * If the app calling this API has enabled autofill services they
649     * will be disabled.
650     */
651    public void disableOwnedAutofillServices() {
652        if (!hasAutofillFeature()) {
653            return;
654        }
655        try {
656            mService.disableOwnedAutofillServices(mContext.getUserId());
657        } catch (RemoteException e) {
658            throw e.rethrowFromSystemServer();
659        }
660    }
661
662    /**
663     * Returns {@code true} if the calling application provides a {@link AutofillService} that is
664     * enabled for the current user, or {@code false} otherwise.
665     */
666    public boolean hasEnabledAutofillServices() {
667        if (mService == null) return false;
668
669        try {
670            return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
671        } catch (RemoteException e) {
672            throw e.rethrowFromSystemServer();
673        }
674    }
675
676    /**
677     * Returns {@code true} if Autofill is supported for this user.
678     *
679     * <p>Autofill is typically supported, but it could be unsupported in cases like:
680     * <ol>
681     *     <li>Low-end devices.
682     *     <li>Device policy rules that forbid its usage.
683     * </ol>
684     */
685    public boolean isAutofillSupported() {
686        if (mService == null) return false;
687
688        try {
689            return mService.isServiceSupported(mContext.getUserId());
690        } catch (RemoteException e) {
691            throw e.rethrowFromSystemServer();
692        }
693    }
694
695    private AutofillClient getClientLocked() {
696        if (mContext instanceof AutofillClient) {
697            return (AutofillClient) mContext;
698        }
699        return null;
700    }
701
702    /** @hide */
703    public void onAuthenticationResult(Intent data) {
704        if (!hasAutofillFeature()) {
705            return;
706        }
707        // TODO: the result code is being ignored, so this method is not reliably
708        // handling the cases where it's not RESULT_OK: it works fine if the service does not
709        // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
710        // service set the extra and returned RESULT_CANCELED...
711
712        if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data);
713
714        synchronized (mLock) {
715            if (mSessionId == NO_SESSION || data == null) {
716                return;
717            }
718            final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
719            final Bundle responseData = new Bundle();
720            responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
721            try {
722                mService.setAuthenticationResult(responseData, mSessionId, mContext.getUserId());
723            } catch (RemoteException e) {
724                Log.e(TAG, "Error delivering authentication result", e);
725            }
726        }
727    }
728
729    private static AutofillId getAutofillId(View view) {
730        return new AutofillId(view.getAccessibilityViewId());
731    }
732
733    private static AutofillId getAutofillId(View parent, int childId) {
734        return new AutofillId(parent.getAccessibilityViewId(), childId);
735    }
736
737    private void startSessionLocked(@NonNull AutofillId id, @NonNull IBinder windowToken,
738            @NonNull Rect bounds, @NonNull AutofillValue value, int flags) {
739        if (DEBUG) {
740            Log.d(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
741                    + ", flags=" + flags);
742        }
743
744        try {
745            mSessionId = mService.startSession(mContext.getActivityToken(), windowToken,
746                    mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
747                    mCallback != null, flags, mContext.getOpPackageName());
748            AutofillClient client = getClientLocked();
749            if (client != null) {
750                client.autofillCallbackResetableStateAvailable();
751            }
752        } catch (RemoteException e) {
753            throw e.rethrowFromSystemServer();
754        }
755    }
756
757    private void finishSessionLocked() {
758        if (DEBUG) {
759            Log.d(TAG, "finishSessionLocked()");
760        }
761
762        try {
763            mService.finishSession(mSessionId, mContext.getUserId());
764        } catch (RemoteException e) {
765            throw e.rethrowFromSystemServer();
766        }
767
768        mTrackedViews = null;
769        mSessionId = NO_SESSION;
770    }
771
772    private void cancelSessionLocked() {
773        if (DEBUG) {
774            Log.d(TAG, "cancelSessionLocked()");
775        }
776
777        try {
778            mService.cancelSession(mSessionId, mContext.getUserId());
779        } catch (RemoteException e) {
780            throw e.rethrowFromSystemServer();
781        }
782
783        mTrackedViews = null;
784        mSessionId = NO_SESSION;
785    }
786
787    private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
788        if (DEBUG) {
789            if (VERBOSE || (flags & FLAG_VIEW_EXITED) != 0) {
790                Log.d(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
791                        + ", value=" + value + ", flags=" + flags);
792            }
793        }
794
795        try {
796            mService.updateSession(mSessionId, id, bounds, value, flags, mContext.getUserId());
797        } catch (RemoteException e) {
798            throw e.rethrowFromSystemServer();
799        }
800    }
801
802    private void ensureServiceClientAddedIfNeededLocked() {
803        if (getClientLocked() == null) {
804            return;
805        }
806
807        if (mServiceClient == null) {
808            mServiceClient = new AutofillManagerClient(this);
809            try {
810                mEnabled = mService.addClient(mServiceClient, mContext.getUserId());
811            } catch (RemoteException e) {
812                throw e.rethrowFromSystemServer();
813            }
814        }
815    }
816
817    /**
818     * Registers a {@link AutofillCallback} to receive autofill events.
819     *
820     * @param callback callback to receive events.
821     */
822    public void registerCallback(@Nullable AutofillCallback callback) {
823        if (!hasAutofillFeature()) {
824            return;
825        }
826        synchronized (mLock) {
827            if (callback == null) return;
828
829            final boolean hadCallback = mCallback != null;
830            mCallback = callback;
831
832            if (!hadCallback) {
833                try {
834                    mService.setHasCallback(mSessionId, mContext.getUserId(), true);
835                } catch (RemoteException e) {
836                    throw e.rethrowFromSystemServer();
837                }
838            }
839        }
840    }
841
842    /**
843     * Unregisters a {@link AutofillCallback} to receive autofill events.
844     *
845     * @param callback callback to stop receiving events.
846     */
847    public void unregisterCallback(@Nullable AutofillCallback callback) {
848        if (!hasAutofillFeature()) {
849            return;
850        }
851        synchronized (mLock) {
852            if (callback == null || mCallback == null || callback != mCallback) return;
853
854            mCallback = null;
855
856            try {
857                mService.setHasCallback(mSessionId, mContext.getUserId(), false);
858            } catch (RemoteException e) {
859                throw e.rethrowFromSystemServer();
860            }
861        }
862    }
863
864    private void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id, int width,
865            int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
866        final View anchor = findAchorView(windowToken, id);
867        if (anchor == null) {
868            return;
869        }
870
871        AutofillCallback callback = null;
872        synchronized (mLock) {
873            if (mSessionId == sessionId) {
874                AutofillClient client = getClientLocked();
875
876                if (client != null) {
877                    if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
878                            anchorBounds, presenter) && mCallback != null) {
879                        callback = mCallback;
880                    }
881                }
882            }
883        }
884
885        if (callback != null) {
886            if (id.isVirtual()) {
887                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
888                        AutofillCallback.EVENT_INPUT_SHOWN);
889            } else {
890                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
891            }
892        }
893    }
894
895    private void authenticate(int sessionId, IntentSender intent, Intent fillInIntent) {
896        synchronized (mLock) {
897            if (sessionId == mSessionId) {
898                AutofillClient client = getClientLocked();
899                if (client != null) {
900                    client.autofillCallbackAuthenticate(intent, fillInIntent);
901                }
902            }
903        }
904    }
905
906    private void setState(boolean enabled) {
907        synchronized (mLock) {
908            mEnabled = enabled;
909        }
910    }
911
912    /**
913     * Sets a view as autofilled if the current value is the {code targetValue}.
914     *
915     * @param view The view that is to be autofilled
916     * @param targetValue The value we want to fill into view
917     */
918    private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
919        AutofillValue currentValue = view.getAutofillValue();
920        if (Objects.equals(currentValue, targetValue)) {
921            synchronized (mLock) {
922                if (mLastAutofilledData == null) {
923                    mLastAutofilledData = new ParcelableMap(1);
924                }
925                mLastAutofilledData.put(getAutofillId(view), targetValue);
926            }
927            view.setAutofilled(true);
928        }
929    }
930
931    private void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
932            List<AutofillValue> values) {
933        synchronized (mLock) {
934            if (sessionId != mSessionId) {
935                return;
936            }
937
938            final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
939            if (root == null) {
940                return;
941            }
942
943            final int itemCount = ids.size();
944            int numApplied = 0;
945            ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
946
947            for (int i = 0; i < itemCount; i++) {
948                final AutofillId id = ids.get(i);
949                final AutofillValue value = values.get(i);
950                final int viewId = id.getViewId();
951                final View view = root.findViewByAccessibilityIdTraversal(viewId);
952                if (view == null) {
953                    Log.w(TAG, "autofill(): no View with id " + viewId);
954                    continue;
955                }
956                if (id.isVirtual()) {
957                    if (virtualValues == null) {
958                        // Most likely there will be just one view with virtual children.
959                        virtualValues = new ArrayMap<>(1);
960                    }
961                    SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
962                    if (valuesByParent == null) {
963                        // We don't know the size yet, but usually it will be just a few fields...
964                        valuesByParent = new SparseArray<>(5);
965                        virtualValues.put(view, valuesByParent);
966                    }
967                    valuesByParent.put(id.getVirtualChildId(), value);
968                } else {
969                    // Mark the view as to be autofilled with 'value'
970                    if (mLastAutofilledData == null) {
971                        mLastAutofilledData = new ParcelableMap(itemCount - i);
972                    }
973                    mLastAutofilledData.put(id, value);
974
975                    view.autofill(value);
976
977                    // Set as autofilled if the values match now, e.g. when the value was updated
978                    // synchronously.
979                    // If autofill happens async, the view is set to autofilled in
980                    // notifyValueChanged.
981                    setAutofilledIfValuesIs(view, value);
982
983                    numApplied++;
984                }
985            }
986
987            if (virtualValues != null) {
988                for (int i = 0; i < virtualValues.size(); i++) {
989                    final View parent = virtualValues.keyAt(i);
990                    final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
991                    parent.autofill(childrenValues);
992                    numApplied += childrenValues.size();
993                }
994            }
995
996            final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
997            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
998            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
999                    numApplied);
1000            mMetricsLogger.write(log);
1001        }
1002    }
1003
1004    /**
1005     *  Set the tracked views.
1006     *
1007     * @param trackedIds The views to be tracked
1008     * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
1009     */
1010    private void setTrackedViews(int sessionId, List<AutofillId> trackedIds,
1011            boolean saveOnAllViewsInvisible) {
1012        synchronized (mLock) {
1013            if (mEnabled && mSessionId == sessionId) {
1014                if (saveOnAllViewsInvisible) {
1015                    mTrackedViews = new TrackedViews(trackedIds);
1016                } else {
1017                    mTrackedViews = null;
1018                }
1019            }
1020        }
1021    }
1022
1023    private void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
1024        final View anchor = findAchorView(windowToken, id);
1025
1026        AutofillCallback callback = null;
1027        synchronized (mLock) {
1028            if (mSessionId == sessionId) {
1029                AutofillClient client = getClientLocked();
1030
1031                if (client != null) {
1032                    if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
1033                        callback = mCallback;
1034                    }
1035                }
1036            }
1037        }
1038
1039        if (callback != null) {
1040            if (id.isVirtual()) {
1041                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1042                        AutofillCallback.EVENT_INPUT_HIDDEN);
1043            } else {
1044                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
1045            }
1046        }
1047    }
1048
1049    private void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
1050        final View anchor = findAchorView(windowToken, id);
1051
1052        AutofillCallback callback = null;
1053        synchronized (mLock) {
1054            if (mSessionId == sessionId && getClientLocked() != null) {
1055                callback = mCallback;
1056            }
1057        }
1058
1059        if (callback != null) {
1060            if (id.isVirtual()) {
1061                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1062                        AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1063            } else {
1064                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1065            }
1066
1067        }
1068    }
1069
1070    private View findAchorView(IBinder windowToken, AutofillId id) {
1071        final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
1072        if (root == null) {
1073            Log.w(TAG, "no window with token " + windowToken);
1074            return null;
1075        }
1076        final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
1077        if (view == null) {
1078            Log.w(TAG, "no view with id " + id);
1079            return null;
1080        }
1081        return view;
1082    }
1083
1084    private boolean hasAutofillFeature() {
1085        return mService != null;
1086    }
1087
1088    /**
1089     * View tracking information. Once all tracked views become invisible the session is finished.
1090     */
1091    private class TrackedViews {
1092        /** Visible tracked views */
1093        @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
1094
1095        /** Invisible tracked views */
1096        @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
1097
1098        /**
1099         * Check if set is null or value is in set.
1100         *
1101         * @param set   The set or null (== empty set)
1102         * @param value The value that might be in the set
1103         *
1104         * @return {@code true} iff set is not empty and value is in set
1105         */
1106        private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
1107            return set != null && set.contains(value);
1108        }
1109
1110        /**
1111         * Add a value to a set. If set is null, create a new set.
1112         *
1113         * @param set        The set or null (== empty set)
1114         * @param valueToAdd The value to add
1115         *
1116         * @return The set including the new value. If set was {@code null}, a set containing only
1117         *         the new value.
1118         */
1119        @NonNull
1120        private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
1121            if (set == null) {
1122                set = new ArraySet<>(1);
1123            }
1124
1125            set.add(valueToAdd);
1126
1127            return set;
1128        }
1129
1130        /**
1131         * Remove a value from a set.
1132         *
1133         * @param set           The set or null (== empty set)
1134         * @param valueToRemove The value to remove
1135         *
1136         * @return The set without the removed value. {@code null} if set was null, or is empty
1137         *         after removal.
1138         */
1139        @Nullable
1140        private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
1141            if (set == null) {
1142                return null;
1143            }
1144
1145            set.remove(valueToRemove);
1146
1147            if (set.isEmpty()) {
1148                return null;
1149            }
1150
1151            return set;
1152        }
1153
1154        /**
1155         * Set the tracked views.
1156         *
1157         * @param trackedIds The views to be tracked
1158         */
1159        TrackedViews(@NonNull List<AutofillId> trackedIds) {
1160            mVisibleTrackedIds = null;
1161            mInvisibleTrackedIds = null;
1162
1163            AutofillClient client = getClientLocked();
1164            if (trackedIds != null) {
1165                int numIds = trackedIds.size();
1166                for (int i = 0; i < numIds; i++) {
1167                    AutofillId id = trackedIds.get(i);
1168
1169                    boolean isVisible = true;
1170                    if (client != null && client.isVisibleForAutofill()) {
1171                        isVisible = client.getViewVisibility(id.getViewId());
1172                    }
1173
1174                    if (isVisible) {
1175                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1176                    } else {
1177                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1178                    }
1179                }
1180            }
1181
1182            if (DEBUG) {
1183                Log.d(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
1184                        + " mVisibleTrackedIds=" + mVisibleTrackedIds
1185                        + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
1186            }
1187
1188            if (mVisibleTrackedIds == null) {
1189                finishSessionLocked();
1190            }
1191        }
1192
1193        /**
1194         * Called when a {@link View view's} visibility changes.
1195         *
1196         * @param view {@link View} that was exited.
1197         * @param isVisible visible if the view is visible in the view hierarchy.
1198         */
1199        void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
1200            AutofillId id = getAutofillId(view);
1201            AutofillClient client = getClientLocked();
1202
1203            if (DEBUG) {
1204                Log.d(TAG, "notifyViewVisibilityChange(): id=" + id + " isVisible="
1205                        + isVisible);
1206            }
1207
1208            if (client != null && client.isVisibleForAutofill()) {
1209                if (isVisible) {
1210                    if (isInSet(mInvisibleTrackedIds, id)) {
1211                        mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
1212                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1213                    }
1214                } else {
1215                    if (isInSet(mVisibleTrackedIds, id)) {
1216                        mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
1217                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1218                    }
1219                }
1220            }
1221
1222            if (mVisibleTrackedIds == null) {
1223                finishSessionLocked();
1224            }
1225        }
1226
1227        /**
1228         * Called once the client becomes visible.
1229         *
1230         * @see AutofillClient#isVisibleForAutofill()
1231         */
1232        void onVisibleForAutofill() {
1233            // The visibility of the views might have changed while the client was not started,
1234            // hence update the visibility state for all views.
1235            AutofillClient client = getClientLocked();
1236            ArraySet<AutofillId> updatedVisibleTrackedIds = null;
1237            ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
1238            if (client != null) {
1239                if (mInvisibleTrackedIds != null) {
1240                    for (AutofillId id : mInvisibleTrackedIds) {
1241                        if (client.getViewVisibility(id.getViewId())) {
1242                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1243
1244                            if (DEBUG) {
1245                                Log.i(TAG, "onVisibleForAutofill() " + id + " became visible");
1246                            }
1247                        } else {
1248                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1249                        }
1250                    }
1251                }
1252
1253                if (mVisibleTrackedIds != null) {
1254                    for (AutofillId id : mVisibleTrackedIds) {
1255                        if (client.getViewVisibility(id.getViewId())) {
1256                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1257                        } else {
1258                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1259
1260                            if (DEBUG) {
1261                                Log.i(TAG, "onVisibleForAutofill() " + id + " became invisible");
1262                            }
1263                        }
1264                    }
1265                }
1266
1267                mInvisibleTrackedIds = updatedInvisibleTrackedIds;
1268                mVisibleTrackedIds = updatedVisibleTrackedIds;
1269            }
1270
1271            if (mVisibleTrackedIds == null) {
1272                finishSessionLocked();
1273            }
1274        }
1275    }
1276
1277    /**
1278     * Callback for auto-fill related events.
1279     *
1280     * <p>Typically used for applications that display their own "auto-complete" views, so they can
1281     * enable / disable such views when the auto-fill UI affordance is shown / hidden.
1282     */
1283    public abstract static class AutofillCallback {
1284
1285        /** @hide */
1286        @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
1287        @Retention(RetentionPolicy.SOURCE)
1288        public @interface AutofillEventType {}
1289
1290        /**
1291         * The auto-fill input UI affordance associated with the view was shown.
1292         *
1293         * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
1294         * should be hidden upon receiving this event.
1295         */
1296        public static final int EVENT_INPUT_SHOWN = 1;
1297
1298        /**
1299         * The auto-fill input UI affordance associated with the view was hidden.
1300         *
1301         * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
1302         * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
1303         */
1304        public static final int EVENT_INPUT_HIDDEN = 2;
1305
1306        /**
1307         * The auto-fill input UI affordance associated with the view won't be shown because
1308         * autofill is not available.
1309         *
1310         * <p>If the view provides its own auto-complete UI affordance but was not displaying it
1311         * to avoid flickering, it could shown it upon receiving this event.
1312         */
1313        public static final int EVENT_INPUT_UNAVAILABLE = 3;
1314
1315        /**
1316         * Called after a change in the autofill state associated with a view.
1317         *
1318         * @param view view associated with the change.
1319         *
1320         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1321         */
1322        public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
1323        }
1324
1325        /**
1326         * Called after a change in the autofill state associated with a virtual view.
1327         *
1328         * @param view parent view associated with the change.
1329         * @param childId id identifying the virtual child inside the parent view.
1330         *
1331         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1332         */
1333        public void onAutofillEvent(@NonNull View view, int childId, @AutofillEventType int event) {
1334        }
1335    }
1336
1337    private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
1338        private final WeakReference<AutofillManager> mAfm;
1339
1340        AutofillManagerClient(AutofillManager autofillManager) {
1341            mAfm = new WeakReference<>(autofillManager);
1342        }
1343
1344        @Override
1345        public void setState(boolean enabled) {
1346            final AutofillManager afm = mAfm.get();
1347            if (afm != null) {
1348                afm.mContext.getMainThreadHandler().post(() -> afm.setState(enabled));
1349            }
1350        }
1351
1352        @Override
1353        public void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
1354                List<AutofillValue> values) {
1355            final AutofillManager afm = mAfm.get();
1356            if (afm != null) {
1357                afm.mContext.getMainThreadHandler().post(
1358                        () -> afm.autofill(sessionId, windowToken, ids, values));
1359            }
1360        }
1361
1362        @Override
1363        public void authenticate(int sessionId, IntentSender intent, Intent fillInIntent) {
1364            final AutofillManager afm = mAfm.get();
1365            if (afm != null) {
1366                afm.mContext.getMainThreadHandler().post(
1367                        () -> afm.authenticate(sessionId, intent, fillInIntent));
1368            }
1369        }
1370
1371        @Override
1372        public void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id,
1373                int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
1374            final AutofillManager afm = mAfm.get();
1375            if (afm != null) {
1376                afm.mContext.getMainThreadHandler().post(
1377                        () -> afm.requestShowFillUi(sessionId, windowToken, id, width, height,
1378                                anchorBounds, presenter));
1379            }
1380        }
1381
1382        @Override
1383        public void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
1384            final AutofillManager afm = mAfm.get();
1385            if (afm != null) {
1386                afm.mContext.getMainThreadHandler().post(
1387                        () -> afm.requestHideFillUi(sessionId, windowToken, id));
1388            }
1389        }
1390
1391        @Override
1392        public void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
1393            final AutofillManager afm = mAfm.get();
1394            if (afm != null) {
1395                afm.mContext.getMainThreadHandler().post(
1396                        () -> afm.notifyNoFillUi(sessionId, windowToken, id));
1397            }
1398        }
1399
1400        @Override
1401        public void startIntentSender(IntentSender intentSender) {
1402            final AutofillManager afm = mAfm.get();
1403            if (afm != null) {
1404                afm.mContext.getMainThreadHandler().post(() -> {
1405                    try {
1406                        afm.mContext.startIntentSender(intentSender, null, 0, 0, 0);
1407                    } catch (IntentSender.SendIntentException e) {
1408                        Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
1409                    }
1410                });
1411            }
1412        }
1413
1414        @Override
1415        public void setTrackedViews(int sessionId, List<AutofillId> ids,
1416                boolean saveOnAllViewsInvisible) {
1417            final AutofillManager afm = mAfm.get();
1418            if (afm != null) {
1419                afm.mContext.getMainThreadHandler().post(
1420                        () -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible)
1421                );
1422            }
1423        }
1424    }
1425}
1426