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