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