AutofillManager.java revision 744976e2985a07f602c02721db8969a638d51c82
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.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
20import static android.view.autofill.Helper.sDebug;
21import static android.view.autofill.Helper.sVerbose;
22
23import android.annotation.IntDef;
24import android.annotation.NonNull;
25import android.annotation.Nullable;
26import android.annotation.SystemService;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentSender;
30import android.graphics.Rect;
31import android.metrics.LogMaker;
32import android.os.Bundle;
33import android.os.Parcelable;
34import android.os.RemoteException;
35import android.service.autofill.AutofillService;
36import android.service.autofill.FillEventHistory;
37import android.util.ArrayMap;
38import android.util.ArraySet;
39import android.util.Log;
40import android.util.SparseArray;
41import android.view.View;
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.ArrayList;
51import java.util.List;
52import java.util.Objects;
53
54/**
55 * The {@link AutofillManager} provides ways for apps and custom views to integrate with the
56 * Autofill Framework lifecycle.
57 *
58 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an
59 * activity context; the autofill context is created when one of the following methods is called for
60 * the first time in an activity context, and the current user has an enabled autofill service:
61 *
62 * <ul>
63 *   <li>{@link #notifyViewEntered(View)}
64 *   <li>{@link #notifyViewEntered(View, int, Rect)}
65 *   <li>{@link #requestAutofill(View)}
66 * </ul>
67 *
68 * <p>Tipically, the context is automatically created when the first view of the activity is
69 * focused because {@code View.onFocusChanged()} indirectly calls
70 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to
71 * explicitly create it (for example, a custom view developer could offer a contextual menu action
72 * in a text-field view to let users manually request autofill).
73 *
74 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure}
75 * that represents the view hierarchy by calling
76 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views
77 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in
78 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and
79 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in
80 * the hierarchy.
81 *
82 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which
83 * parses it looking for views that can be autofilled. If the service finds such views, it returns
84 * a data structure to the Android System containing the following optional info:
85 *
86 * <ul>
87 *   <li>Datasets used to autofill subsets of views in the activity.
88 *   <li>Id of views that the service can save their values for future autofilling.
89 * </ul>
90 *
91 * <p>When the service returns datasets, the Android System displays an autofill dataset picker
92 * UI affordance associated with the view, when the view is focused on and is part of a dataset.
93 * The application can be notified when the affordance is shown by registering an
94 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
95 * selects a dataset from the affordance, all views present in the dataset are autofilled, through
96 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
97 *
98 * <p>When the service returns ids of savable views, the Android System keeps track of changes
99 * made to these views, so they can be used to determine if the autofill save UI is shown later.
100 *
101 * <p>The context is then finished when one of the following occurs:
102 *
103 * <ul>
104 *   <li>{@link #commit()} is called or all savable views are gone.
105 *   <li>{@link #cancel()} is called.
106 * </ul>
107 *
108 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
109 * shows a save UI affordance if the value of savable views have changed. If the user selects the
110 * option to Save, the current value of the views is then sent to the autofill service.
111 *
112 * <p>It is safe to call into its methods from any thread.
113 */
114@SystemService(Context.AUTOFILL_MANAGER_SERVICE)
115public final class AutofillManager {
116
117    private static final String TAG = "AutofillManager";
118
119    /**
120     * Intent extra: The assist structure which captures the filled screen.
121     *
122     * <p>
123     * Type: {@link android.app.assist.AssistStructure}
124     */
125    public static final String EXTRA_ASSIST_STRUCTURE =
126            "android.view.autofill.extra.ASSIST_STRUCTURE";
127
128    /**
129     * Intent extra: The result of an authentication operation. It is
130     * either a fully populated {@link android.service.autofill.FillResponse}
131     * or a fully populated {@link android.service.autofill.Dataset} if
132     * a response or a dataset is being authenticated respectively.
133     *
134     * <p>
135     * Type: {@link android.service.autofill.FillResponse} or a
136     * {@link android.service.autofill.Dataset}
137     */
138    public static final String EXTRA_AUTHENTICATION_RESULT =
139            "android.view.autofill.extra.AUTHENTICATION_RESULT";
140
141    /**
142     * Intent extra: The optional extras provided by the
143     * {@link android.service.autofill.AutofillService}.
144     *
145     * <p>For example, when the service responds to a {@link
146     * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
147     * a {@code FillResponse} that requires authentication, the Intent that launches the
148     * service authentication will contain the Bundle set by
149     * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
150     *
151     * <p>
152     * Type: {@link android.os.Bundle}
153     */
154    public static final String EXTRA_CLIENT_STATE =
155            "android.view.autofill.extra.CLIENT_STATE";
156
157    static final String SESSION_ID_TAG = "android:sessionId";
158    static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
159
160    /** @hide */ public static final int ACTION_START_SESSION = 1;
161    /** @hide */ public static final int ACTION_VIEW_ENTERED =  2;
162    /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
163    /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
164
165
166    /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
167    /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
168    /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
169
170    /** Which bits in an authentication id are used for the dataset id */
171    private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
172    /** How many bits in an authentication id are used for the dataset id */
173    private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
174    /** @hide The index for an undefined data set */
175    public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
176
177    /**
178     * Makes an authentication id from a request id and a dataset id.
179     *
180     * @param requestId The request id.
181     * @param datasetId The dataset id.
182     * @return The authentication id.
183     * @hide
184     */
185    public static int makeAuthenticationId(int requestId, int datasetId) {
186        return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT)
187                | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK);
188    }
189
190    /**
191     * Gets the request id from an authentication id.
192     *
193     * @param authRequestId The authentication id.
194     * @return The request id.
195     * @hide
196     */
197    public static int getRequestIdFromAuthenticationId(int authRequestId) {
198        return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT);
199    }
200
201    /**
202     * Gets the dataset id from an authentication id.
203     *
204     * @param authRequestId The authentication id.
205     * @return The dataset id.
206     * @hide
207     */
208    public static int getDatasetIdFromAuthenticationId(int authRequestId) {
209        return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK);
210    }
211
212    private final MetricsLogger mMetricsLogger = new MetricsLogger();
213
214    /**
215     * There is currently no session running.
216     * {@hide}
217     */
218    public static final int NO_SESSION = Integer.MIN_VALUE;
219
220    private final IAutoFillManager mService;
221
222    private final Object mLock = new Object();
223
224    @GuardedBy("mLock")
225    private IAutoFillManagerClient mServiceClient;
226
227    @GuardedBy("mLock")
228    private AutofillCallback mCallback;
229
230    private final Context mContext;
231
232    @GuardedBy("mLock")
233    private int mSessionId = NO_SESSION;
234
235    @GuardedBy("mLock")
236    private boolean mEnabled;
237
238    /** If a view changes to this mapping the autofill operation was successful */
239    @GuardedBy("mLock")
240    @Nullable private ParcelableMap mLastAutofilledData;
241
242    /** If view tracking is enabled, contains the tracking state */
243    @GuardedBy("mLock")
244    @Nullable private TrackedViews mTrackedViews;
245
246    /** Views that are only tracked because they are fillable and could be anchoring the UI. */
247    @GuardedBy("mLock")
248    @Nullable private ArraySet<AutofillId> mFillableIds;
249
250    /** @hide */
251    public interface AutofillClient {
252        /**
253         * Asks the client to start an authentication flow.
254         *
255         * @param authenticationId A unique id of the authentication operation.
256         * @param intent The authentication intent.
257         * @param fillInIntent The authentication fill-in intent.
258         */
259        void autofillCallbackAuthenticate(int authenticationId, IntentSender intent,
260                Intent fillInIntent);
261
262        /**
263         * Tells the client this manager has state to be reset.
264         */
265        void autofillCallbackResetableStateAvailable();
266
267        /**
268         * Request showing the autofill UI.
269         *
270         * @param anchor The real view the UI needs to anchor to.
271         * @param width The width of the fill UI content.
272         * @param height The height of the fill UI content.
273         * @param virtualBounds The bounds of the virtual decendant of the anchor.
274         * @param presenter The presenter that controls the fill UI window.
275         * @return Whether the UI was shown.
276         */
277        boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
278                @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
279
280        /**
281         * Request hiding the autofill UI.
282         *
283         * @return Whether the UI was hidden.
284         */
285        boolean autofillCallbackRequestHideFillUi();
286
287        /**
288         * Checks if views are currently attached and visible.
289         *
290         * @return And array with {@code true} iff the view is attached or visible
291         */
292        @NonNull boolean[] getViewVisibility(@NonNull int[] viewId);
293
294        /**
295         * Checks is the client is currently visible as understood by autofill.
296         *
297         * @return {@code true} if the client is currently visible
298         */
299        boolean isVisibleForAutofill();
300
301        /**
302         * Finds views by traversing the hierarchies of the client.
303         *
304         * @param viewIds The autofill ids of the views to find
305         *
306         * @return And array containing the views (empty if no views found).
307         */
308        @NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds);
309
310        /**
311         * Finds a view by traversing the hierarchies of the client.
312         *
313         * @param viewId The autofill id of the views to find
314         *
315         * @return The view, or {@code null} if not found
316         */
317        @Nullable View findViewByAutofillIdTraversal(int viewId);
318
319        /**
320         * Runs the specified action on the UI thread.
321         */
322        void runOnUiThread(Runnable action);
323    }
324
325    /**
326     * @hide
327     */
328    public AutofillManager(Context context, IAutoFillManager service) {
329        mContext = context;
330        mService = service;
331    }
332
333    /**
334     * Restore state after activity lifecycle
335     *
336     * @param savedInstanceState The state to be restored
337     *
338     * {@hide}
339     */
340    public void onCreate(Bundle savedInstanceState) {
341        if (!hasAutofillFeature()) {
342            return;
343        }
344        synchronized (mLock) {
345            mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
346
347            if (mSessionId != NO_SESSION) {
348                Log.w(TAG, "New session was started before onCreate()");
349                return;
350            }
351
352            mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
353
354            if (mSessionId != NO_SESSION) {
355                ensureServiceClientAddedIfNeededLocked();
356
357                final AutofillClient client = getClientLocked();
358                if (client != null) {
359                    try {
360                        final boolean sessionWasRestored = mService.restoreSession(mSessionId,
361                                mContext.getActivityToken(), mServiceClient.asBinder());
362
363                        if (!sessionWasRestored) {
364                            Log.w(TAG, "Session " + mSessionId + " could not be restored");
365                            mSessionId = NO_SESSION;
366                        } else {
367                            if (sDebug) {
368                                Log.d(TAG, "session " + mSessionId + " was restored");
369                            }
370
371                            client.autofillCallbackResetableStateAvailable();
372                        }
373                    } catch (RemoteException e) {
374                        Log.e(TAG, "Could not figure out if there was an autofill session", e);
375                    }
376                }
377            }
378        }
379    }
380
381    /**
382     * Called once the client becomes visible.
383     *
384     * @see AutofillClient#isVisibleForAutofill()
385     *
386     * {@hide}
387     */
388    public void onVisibleForAutofill() {
389        synchronized (mLock) {
390            if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
391                mTrackedViews.onVisibleForAutofillLocked();
392            }
393        }
394    }
395
396    /**
397     * Save state before activity lifecycle
398     *
399     * @param outState Place to store the state
400     *
401     * {@hide}
402     */
403    public void onSaveInstanceState(Bundle outState) {
404        if (!hasAutofillFeature()) {
405            return;
406        }
407        synchronized (mLock) {
408            if (mSessionId != NO_SESSION) {
409                outState.putInt(SESSION_ID_TAG, mSessionId);
410            }
411
412            if (mLastAutofilledData != null) {
413                outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
414            }
415        }
416    }
417
418    /**
419     * Checks whether autofill is enabled for the current user.
420     *
421     * <p>Typically used to determine whether the option to explicitly request autofill should
422     * be offered - see {@link #requestAutofill(View)}.
423     *
424     * @return whether autofill is enabled for the current user.
425     */
426    public boolean isEnabled() {
427        if (!hasAutofillFeature()) {
428            return false;
429        }
430        synchronized (mLock) {
431            ensureServiceClientAddedIfNeededLocked();
432            return mEnabled;
433        }
434    }
435
436    /**
437     * Should always be called from {@link AutofillService#getFillEventHistory()}.
438     *
439     * @hide
440     */
441    @Nullable public FillEventHistory getFillEventHistory() {
442        try {
443            return mService.getFillEventHistory();
444        } catch (RemoteException e) {
445            e.rethrowFromSystemServer();
446            return null;
447        }
448    }
449
450    /**
451     * Explicitly requests a new autofill context.
452     *
453     * <p>Normally, the autofill context is automatically started if necessary when
454     * {@link #notifyViewEntered(View)} is called, but this method should be used in the
455     * cases where it must be explicitly started. For example, when the view offers an AUTOFILL
456     * option on its contextual overflow menu, and the user selects it.
457     *
458     * @param view view requesting the new autofill context.
459     */
460    public void requestAutofill(@NonNull View view) {
461        notifyViewEntered(view, FLAG_MANUAL_REQUEST);
462    }
463
464    /**
465     * Explicitly requests a new autofill context for virtual views.
466     *
467     * <p>Normally, the autofill context is automatically started if necessary when
468     * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the
469     * cases where it must be explicitly started. For example, when the virtual view offers an
470     * AUTOFILL option on its contextual overflow menu, and the user selects it.
471     *
472     * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
473     * parent view uses {@code bounds} to draw the virtual view inside its Canvas,
474     * the absolute bounds could be calculated by:
475     *
476     * <pre class="prettyprint">
477     *   int offset[] = new int[2];
478     *   getLocationOnScreen(offset);
479     *   Rect absBounds = new Rect(bounds.left + offset[0],
480     *       bounds.top + offset[1],
481     *       bounds.right + offset[0], bounds.bottom + offset[1]);
482     * </pre>
483     *
484     * @param view the virtual view parent.
485     * @param virtualId id identifying the virtual child inside the parent view.
486     * @param absBounds absolute boundaries of the virtual view in the screen.
487     */
488    public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
489        notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST);
490    }
491
492    /**
493     * Called when a {@link View} that supports autofill is entered.
494     *
495     * @param view {@link View} that was entered.
496     */
497    public void notifyViewEntered(@NonNull View view) {
498        notifyViewEntered(view, 0);
499    }
500
501    private void notifyViewEntered(@NonNull View view, int flags) {
502        if (!hasAutofillFeature()) {
503            return;
504        }
505        AutofillCallback callback = null;
506        synchronized (mLock) {
507            ensureServiceClientAddedIfNeededLocked();
508
509            if (!mEnabled) {
510                if (mCallback != null) {
511                    callback = mCallback;
512                }
513            } else {
514                final AutofillId id = getAutofillId(view);
515                final AutofillValue value = view.getAutofillValue();
516
517                if (mSessionId == NO_SESSION) {
518                    // Starts new session.
519                    startSessionLocked(id, null, value, flags);
520                } else {
521                    // Update focus on existing session.
522                    updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
523                }
524            }
525        }
526
527        if (callback != null) {
528            mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
529        }
530    }
531
532    /**
533     * Called when a {@link View} that supports autofill is exited.
534     *
535     * @param view {@link View} that was exited.
536     */
537    public void notifyViewExited(@NonNull View view) {
538        if (!hasAutofillFeature()) {
539            return;
540        }
541        synchronized (mLock) {
542            ensureServiceClientAddedIfNeededLocked();
543
544            if (mEnabled && mSessionId != NO_SESSION) {
545                final AutofillId id = getAutofillId(view);
546
547                // Update focus on existing session.
548                updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
549            }
550        }
551    }
552
553    /**
554     * Called when a {@link View view's} visibility changes.
555     *
556     * @param view {@link View} that was exited.
557     * @param isVisible visible if the view is visible in the view hierarchy.
558     *
559     * @hide
560     */
561    public void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
562        synchronized (mLock) {
563            if (mEnabled && mSessionId != NO_SESSION) {
564                if (!isVisible && mFillableIds != null) {
565                    final AutofillId id = view.getAutofillId();
566                    if (mFillableIds.contains(id)) {
567                        if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible");
568                        requestHideFillUi(id, view);
569                    }
570                }
571                if (mTrackedViews != null) {
572                    mTrackedViews.notifyViewVisibilityChange(view, isVisible);
573                }
574            }
575        }
576    }
577
578    /**
579     * Called when a virtual view that supports autofill is entered.
580     *
581     * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
582     * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas,
583     * the absolute bounds could be calculated by:
584     *
585     * <pre class="prettyprint">
586     *   int offset[] = new int[2];
587     *   getLocationOnScreen(offset);
588     *   Rect absBounds = new Rect(bounds.left + offset[0],
589     *       bounds.top + offset[1],
590     *       bounds.right + offset[0], bounds.bottom + offset[1]);
591     * </pre>
592     *
593     * @param view the virtual view parent.
594     * @param virtualId id identifying the virtual child inside the parent view.
595     * @param absBounds absolute boundaries of the virtual view in the screen.
596     */
597    public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
598        notifyViewEntered(view, virtualId, absBounds, 0);
599    }
600
601    private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) {
602        if (!hasAutofillFeature()) {
603            return;
604        }
605        AutofillCallback callback = null;
606        synchronized (mLock) {
607            ensureServiceClientAddedIfNeededLocked();
608
609            if (!mEnabled) {
610                if (mCallback != null) {
611                    callback = mCallback;
612                }
613            } else {
614                final AutofillId id = getAutofillId(view, virtualId);
615
616                if (mSessionId == NO_SESSION) {
617                    // Starts new session.
618                    startSessionLocked(id, bounds, null, flags);
619                } else {
620                    // Update focus on existing session.
621                    updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
622                }
623            }
624        }
625
626        if (callback != null) {
627            callback.onAutofillEvent(view, virtualId,
628                    AutofillCallback.EVENT_INPUT_UNAVAILABLE);
629        }
630    }
631
632    /**
633     * Called when a virtual view that supports autofill is exited.
634     *
635     * @param view the virtual view parent.
636     * @param virtualId id identifying the virtual child inside the parent view.
637     */
638    public void notifyViewExited(@NonNull View view, int virtualId) {
639        if (!hasAutofillFeature()) {
640            return;
641        }
642        synchronized (mLock) {
643            ensureServiceClientAddedIfNeededLocked();
644
645            if (mEnabled && mSessionId != NO_SESSION) {
646                final AutofillId id = getAutofillId(view, virtualId);
647
648                // Update focus on existing session.
649                updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
650            }
651        }
652    }
653
654    /**
655     * Called to indicate the value of an autofillable {@link View} changed.
656     *
657     * @param view view whose value changed.
658     */
659    public void notifyValueChanged(View view) {
660        if (!hasAutofillFeature()) {
661            return;
662        }
663        AutofillId id = null;
664        boolean valueWasRead = false;
665        AutofillValue value = null;
666
667        synchronized (mLock) {
668            // If the session is gone some fields might still be highlighted, hence we have to
669            // remove the isAutofilled property even if no sessions are active.
670            if (mLastAutofilledData == null) {
671                view.setAutofilled(false);
672            } else {
673                id = getAutofillId(view);
674                if (mLastAutofilledData.containsKey(id)) {
675                    value = view.getAutofillValue();
676                    valueWasRead = true;
677
678                    if (Objects.equals(mLastAutofilledData.get(id), value)) {
679                        view.setAutofilled(true);
680                    } else {
681                        view.setAutofilled(false);
682                        mLastAutofilledData.remove(id);
683                    }
684                } else {
685                    view.setAutofilled(false);
686                }
687            }
688
689            if (!mEnabled || mSessionId == NO_SESSION) {
690                return;
691            }
692
693            if (id == null) {
694                id = getAutofillId(view);
695            }
696
697            if (!valueWasRead) {
698                value = view.getAutofillValue();
699            }
700
701            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
702        }
703    }
704
705    /**
706     * Called to indicate the value of an autofillable virtual view has changed.
707     *
708     * @param view the virtual view parent.
709     * @param virtualId id identifying the virtual child inside the parent view.
710     * @param value new value of the child.
711     */
712    public void notifyValueChanged(View view, int virtualId, AutofillValue value) {
713        if (!hasAutofillFeature()) {
714            return;
715        }
716        synchronized (mLock) {
717            if (!mEnabled || mSessionId == NO_SESSION) {
718                return;
719            }
720
721            final AutofillId id = getAutofillId(view, virtualId);
722            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
723        }
724    }
725
726    /**
727     * Called to indicate the current autofill context should be commited.
728     *
729     * <p>This method is typically called by {@link View Views} that manage virtual views; for
730     * example, when the view is rendering an {@code HTML} page with a form and virtual views
731     * that represent the HTML elements, it should call this method after the form is submitted and
732     * another page is rendered.
733     *
734     * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
735     * methods such as {@link android.app.Activity#finish()}.
736     */
737    public void commit() {
738        if (!hasAutofillFeature()) {
739            return;
740        }
741        synchronized (mLock) {
742            if (!mEnabled && mSessionId == NO_SESSION) {
743                return;
744            }
745
746            finishSessionLocked();
747        }
748    }
749
750    /**
751     * Called to indicate the current autofill context should be cancelled.
752     *
753     * <p>This method is typically called by {@link View Views} that manage virtual views; for
754     * example, when the view is rendering an {@code HTML} page with a form and virtual views
755     * that represent the HTML elements, it should call this method if the user does not post the
756     * form but moves to another form in this page.
757     *
758     * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
759     * methods such as {@link android.app.Activity#finish()}.
760     */
761    public void cancel() {
762        if (!hasAutofillFeature()) {
763            return;
764        }
765        synchronized (mLock) {
766            if (!mEnabled && mSessionId == NO_SESSION) {
767                return;
768            }
769
770            cancelSessionLocked();
771        }
772    }
773
774    /** @hide */
775    public void disableOwnedAutofillServices() {
776        disableAutofillServices();
777    }
778
779    /**
780     * If the app calling this API has enabled autofill services they
781     * will be disabled.
782     */
783    public void disableAutofillServices() {
784        if (!hasAutofillFeature()) {
785            return;
786        }
787        try {
788            mService.disableOwnedAutofillServices(mContext.getUserId());
789        } catch (RemoteException e) {
790            throw e.rethrowFromSystemServer();
791        }
792    }
793
794    /**
795     * Returns {@code true} if the calling application provides a {@link AutofillService} that is
796     * enabled for the current user, or {@code false} otherwise.
797     */
798    public boolean hasEnabledAutofillServices() {
799        if (mService == null) return false;
800
801        try {
802            return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
803        } catch (RemoteException e) {
804            throw e.rethrowFromSystemServer();
805        }
806    }
807
808    /**
809     * Returns {@code true} if autofill is supported by the current device and
810     * is supported for this user.
811     *
812     * <p>Autofill is typically supported, but it could be unsupported in cases like:
813     * <ol>
814     *     <li>Low-end devices.
815     *     <li>Device policy rules that forbid its usage.
816     * </ol>
817     */
818    public boolean isAutofillSupported() {
819        if (mService == null) return false;
820
821        try {
822            return mService.isServiceSupported(mContext.getUserId());
823        } catch (RemoteException e) {
824            throw e.rethrowFromSystemServer();
825        }
826    }
827
828    private AutofillClient getClientLocked() {
829        if (mContext instanceof AutofillClient) {
830            return (AutofillClient) mContext;
831        }
832        return null;
833    }
834
835    /** @hide */
836    public void onAuthenticationResult(int authenticationId, Intent data) {
837        if (!hasAutofillFeature()) {
838            return;
839        }
840        // TODO: the result code is being ignored, so this method is not reliably
841        // handling the cases where it's not RESULT_OK: it works fine if the service does not
842        // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
843        // service set the extra and returned RESULT_CANCELED...
844
845        if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data);
846
847        synchronized (mLock) {
848            if (mSessionId == NO_SESSION || data == null) {
849                return;
850            }
851            final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
852            final Bundle responseData = new Bundle();
853            responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
854            try {
855                mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
856                        mContext.getUserId());
857            } catch (RemoteException e) {
858                Log.e(TAG, "Error delivering authentication result", e);
859            }
860        }
861    }
862
863    private static AutofillId getAutofillId(View view) {
864        return new AutofillId(view.getAutofillViewId());
865    }
866
867    private static AutofillId getAutofillId(View parent, int virtualId) {
868        return new AutofillId(parent.getAutofillViewId(), virtualId);
869    }
870
871    private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
872            @NonNull AutofillValue value, int flags) {
873        if (sVerbose) {
874            Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
875                    + ", flags=" + flags);
876        }
877
878        try {
879            mSessionId = mService.startSession(mContext.getActivityToken(),
880                    mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
881                    mCallback != null, flags, mContext.getOpPackageName());
882            final AutofillClient client = getClientLocked();
883            if (client != null) {
884                client.autofillCallbackResetableStateAvailable();
885            }
886        } catch (RemoteException e) {
887            throw e.rethrowFromSystemServer();
888        }
889    }
890
891    private void finishSessionLocked() {
892        if (sVerbose) Log.v(TAG, "finishSessionLocked()");
893
894        try {
895            mService.finishSession(mSessionId, mContext.getUserId());
896        } catch (RemoteException e) {
897            throw e.rethrowFromSystemServer();
898        }
899
900        mTrackedViews = null;
901        mSessionId = NO_SESSION;
902    }
903
904    private void cancelSessionLocked() {
905        if (sVerbose) Log.v(TAG, "cancelSessionLocked()");
906
907        try {
908            mService.cancelSession(mSessionId, mContext.getUserId());
909        } catch (RemoteException e) {
910            throw e.rethrowFromSystemServer();
911        }
912
913        resetSessionLocked();
914    }
915
916    private void resetSessionLocked() {
917        mSessionId = NO_SESSION;
918        mTrackedViews = null;
919    }
920
921    private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
922            int flags) {
923        if (sVerbose && action != ACTION_VIEW_EXITED) {
924            Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
925                    + ", value=" + value + ", action=" + action + ", flags=" + flags);
926        }
927
928        boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0;
929
930        try {
931            if (restartIfNecessary) {
932                final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
933                        mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
934                        mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
935                if (newId != mSessionId) {
936                    if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
937                    mSessionId = newId;
938                    final AutofillClient client = getClientLocked();
939                    if (client != null) {
940                        client.autofillCallbackResetableStateAvailable();
941                    }
942                }
943            } else {
944                mService.updateSession(mSessionId, id, bounds, value, action, flags,
945                        mContext.getUserId());
946            }
947
948        } catch (RemoteException e) {
949            throw e.rethrowFromSystemServer();
950        }
951    }
952
953    private void ensureServiceClientAddedIfNeededLocked() {
954        if (getClientLocked() == null) {
955            return;
956        }
957
958        if (mServiceClient == null) {
959            mServiceClient = new AutofillManagerClient(this);
960            try {
961                final int flags = mService.addClient(mServiceClient, mContext.getUserId());
962                mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
963                sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
964                sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
965            } catch (RemoteException e) {
966                throw e.rethrowFromSystemServer();
967            }
968        }
969    }
970
971    /**
972     * Registers a {@link AutofillCallback} to receive autofill events.
973     *
974     * @param callback callback to receive events.
975     */
976    public void registerCallback(@Nullable AutofillCallback callback) {
977        if (!hasAutofillFeature()) {
978            return;
979        }
980        synchronized (mLock) {
981            if (callback == null) return;
982
983            final boolean hadCallback = mCallback != null;
984            mCallback = callback;
985
986            if (!hadCallback) {
987                try {
988                    mService.setHasCallback(mSessionId, mContext.getUserId(), true);
989                } catch (RemoteException e) {
990                    throw e.rethrowFromSystemServer();
991                }
992            }
993        }
994    }
995
996    /**
997     * Unregisters a {@link AutofillCallback} to receive autofill events.
998     *
999     * @param callback callback to stop receiving events.
1000     */
1001    public void unregisterCallback(@Nullable AutofillCallback callback) {
1002        if (!hasAutofillFeature()) {
1003            return;
1004        }
1005        synchronized (mLock) {
1006            if (callback == null || mCallback == null || callback != mCallback) return;
1007
1008            mCallback = null;
1009
1010            try {
1011                mService.setHasCallback(mSessionId, mContext.getUserId(), false);
1012            } catch (RemoteException e) {
1013                throw e.rethrowFromSystemServer();
1014            }
1015        }
1016    }
1017
1018    private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1019            Rect anchorBounds, IAutofillWindowPresenter presenter) {
1020        final View anchor = findView(id);
1021        if (anchor == null) {
1022            return;
1023        }
1024
1025        AutofillCallback callback = null;
1026        synchronized (mLock) {
1027            if (mSessionId == sessionId) {
1028                AutofillClient client = getClientLocked();
1029
1030                if (client != null) {
1031                    if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
1032                            anchorBounds, presenter) && mCallback != null) {
1033                        callback = mCallback;
1034                    }
1035                }
1036            }
1037        }
1038
1039        if (callback != null) {
1040            if (id.isVirtual()) {
1041                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1042                        AutofillCallback.EVENT_INPUT_SHOWN);
1043            } else {
1044                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
1045            }
1046        }
1047    }
1048
1049    private void authenticate(int sessionId, int authenticationId, IntentSender intent,
1050            Intent fillInIntent) {
1051        synchronized (mLock) {
1052            if (sessionId == mSessionId) {
1053                AutofillClient client = getClientLocked();
1054                if (client != null) {
1055                    client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent);
1056                }
1057            }
1058        }
1059    }
1060
1061    private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
1062        synchronized (mLock) {
1063            mEnabled = enabled;
1064            if (!mEnabled || resetSession) {
1065                // Reset the session state
1066                resetSessionLocked();
1067            }
1068            if (resetClient) {
1069                // Reset connection to system
1070                mServiceClient = null;
1071            }
1072        }
1073    }
1074
1075    /**
1076     * Sets a view as autofilled if the current value is the {code targetValue}.
1077     *
1078     * @param view The view that is to be autofilled
1079     * @param targetValue The value we want to fill into view
1080     */
1081    private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
1082        AutofillValue currentValue = view.getAutofillValue();
1083        if (Objects.equals(currentValue, targetValue)) {
1084            synchronized (mLock) {
1085                if (mLastAutofilledData == null) {
1086                    mLastAutofilledData = new ParcelableMap(1);
1087                }
1088                mLastAutofilledData.put(getAutofillId(view), targetValue);
1089            }
1090            view.setAutofilled(true);
1091        }
1092    }
1093
1094    private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
1095        synchronized (mLock) {
1096            if (sessionId != mSessionId) {
1097                return;
1098            }
1099
1100            final AutofillClient client = getClientLocked();
1101            if (client == null) {
1102                return;
1103            }
1104
1105            final int itemCount = ids.size();
1106            int numApplied = 0;
1107            ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
1108            final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
1109
1110            for (int i = 0; i < itemCount; i++) {
1111                final AutofillId id = ids.get(i);
1112                final AutofillValue value = values.get(i);
1113                final int viewId = id.getViewId();
1114                final View view = views[i];
1115                if (view == null) {
1116                    Log.w(TAG, "autofill(): no View with id " + viewId);
1117                    continue;
1118                }
1119                if (id.isVirtual()) {
1120                    if (virtualValues == null) {
1121                        // Most likely there will be just one view with virtual children.
1122                        virtualValues = new ArrayMap<>(1);
1123                    }
1124                    SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
1125                    if (valuesByParent == null) {
1126                        // We don't know the size yet, but usually it will be just a few fields...
1127                        valuesByParent = new SparseArray<>(5);
1128                        virtualValues.put(view, valuesByParent);
1129                    }
1130                    valuesByParent.put(id.getVirtualChildId(), value);
1131                } else {
1132                    // Mark the view as to be autofilled with 'value'
1133                    if (mLastAutofilledData == null) {
1134                        mLastAutofilledData = new ParcelableMap(itemCount - i);
1135                    }
1136                    mLastAutofilledData.put(id, value);
1137
1138                    view.autofill(value);
1139
1140                    // Set as autofilled if the values match now, e.g. when the value was updated
1141                    // synchronously.
1142                    // If autofill happens async, the view is set to autofilled in
1143                    // notifyValueChanged.
1144                    setAutofilledIfValuesIs(view, value);
1145
1146                    numApplied++;
1147                }
1148            }
1149
1150            if (virtualValues != null) {
1151                for (int i = 0; i < virtualValues.size(); i++) {
1152                    final View parent = virtualValues.keyAt(i);
1153                    final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
1154                    parent.autofill(childrenValues);
1155                    numApplied += childrenValues.size();
1156                }
1157            }
1158
1159            final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
1160            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
1161            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
1162                    numApplied);
1163            mMetricsLogger.write(log);
1164        }
1165    }
1166
1167    /**
1168     *  Set the tracked views.
1169     *
1170     * @param trackedIds The views to be tracked
1171     * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
1172     * @param fillableIds Views that might anchor FillUI.
1173     */
1174    private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
1175            boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
1176        synchronized (mLock) {
1177            if (mEnabled && mSessionId == sessionId) {
1178                if (saveOnAllViewsInvisible) {
1179                    mTrackedViews = new TrackedViews(trackedIds);
1180                } else {
1181                    mTrackedViews = null;
1182                }
1183                if (fillableIds != null) {
1184                    if (mFillableIds == null) {
1185                        mFillableIds = new ArraySet<>(fillableIds.length);
1186                    }
1187                    for (AutofillId id : fillableIds) {
1188                        mFillableIds.add(id);
1189                    }
1190                    if (sVerbose) {
1191                        Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
1192                                + ", mFillableIds" + mFillableIds);
1193                    }
1194                }
1195            }
1196        }
1197    }
1198
1199    private void requestHideFillUi(AutofillId id) {
1200        final View anchor = findView(id);
1201        if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
1202        if (anchor == null) {
1203            return;
1204        }
1205        requestHideFillUi(id, anchor);
1206    }
1207
1208    private void requestHideFillUi(AutofillId id, View anchor) {
1209
1210        AutofillCallback callback = null;
1211        synchronized (mLock) {
1212            // We do not check the session id for two reasons:
1213            // 1. If local and remote session id are off sync the UI would be stuck shown
1214            // 2. There is a race between the user state being destroyed due the fill
1215            //    service being uninstalled and the UI being dismissed.
1216            AutofillClient client = getClientLocked();
1217            if (client != null) {
1218                if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
1219                    callback = mCallback;
1220                }
1221            }
1222        }
1223
1224        if (callback != null) {
1225            if (id.isVirtual()) {
1226                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1227                        AutofillCallback.EVENT_INPUT_HIDDEN);
1228            } else {
1229                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
1230            }
1231        }
1232    }
1233
1234    private void notifyNoFillUi(int sessionId, AutofillId id) {
1235        final View anchor = findView(id);
1236        if (anchor == null) {
1237            return;
1238        }
1239
1240        AutofillCallback callback = null;
1241        synchronized (mLock) {
1242            if (mSessionId == sessionId && getClientLocked() != null) {
1243                callback = mCallback;
1244            }
1245        }
1246
1247        if (callback != null) {
1248            if (id.isVirtual()) {
1249                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1250                        AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1251            } else {
1252                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1253            }
1254
1255        }
1256    }
1257
1258    /**
1259     * Get an array of viewIds from a List of {@link AutofillId}.
1260     *
1261     * @param autofillIds The autofill ids to convert
1262     *
1263     * @return The array of viewIds.
1264     */
1265    // TODO: move to Helper as static method
1266    @NonNull private int[] getViewIds(@NonNull AutofillId[] autofillIds) {
1267        final int numIds = autofillIds.length;
1268        final int[] viewIds = new int[numIds];
1269        for (int i = 0; i < numIds; i++) {
1270            viewIds[i] = autofillIds[i].getViewId();
1271        }
1272
1273        return viewIds;
1274    }
1275
1276    // TODO: move to Helper as static method
1277    @NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) {
1278        final int numIds = autofillIds.size();
1279        final int[] viewIds = new int[numIds];
1280        for (int i = 0; i < numIds; i++) {
1281            viewIds[i] = autofillIds.get(i).getViewId();
1282        }
1283
1284        return viewIds;
1285    }
1286
1287    /**
1288     * Find a single view by its id.
1289     *
1290     * @param autofillId The autofill id of the view
1291     *
1292     * @return The view or {@code null} if view was not found
1293     */
1294    private View findView(@NonNull AutofillId autofillId) {
1295        final AutofillClient client = getClientLocked();
1296
1297        if (client == null) {
1298            return null;
1299        }
1300
1301        return client.findViewByAutofillIdTraversal(autofillId.getViewId());
1302    }
1303
1304    /** @hide */
1305    public boolean hasAutofillFeature() {
1306        return mService != null;
1307    }
1308
1309    private void post(Runnable runnable) {
1310        final AutofillClient client = getClientLocked();
1311        if (client == null) {
1312            if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
1313            return;
1314        }
1315        client.runOnUiThread(runnable);
1316    }
1317
1318    /**
1319     * View tracking information. Once all tracked views become invisible the session is finished.
1320     */
1321    private class TrackedViews {
1322        /** Visible tracked views */
1323        @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
1324
1325        /** Invisible tracked views */
1326        @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
1327
1328        /**
1329         * Check if set is null or value is in set.
1330         *
1331         * @param set   The set or null (== empty set)
1332         * @param value The value that might be in the set
1333         *
1334         * @return {@code true} iff set is not empty and value is in set
1335         */
1336        // TODO: move to Helper as static method
1337        private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
1338            return set != null && set.contains(value);
1339        }
1340
1341        /**
1342         * Add a value to a set. If set is null, create a new set.
1343         *
1344         * @param set        The set or null (== empty set)
1345         * @param valueToAdd The value to add
1346         *
1347         * @return The set including the new value. If set was {@code null}, a set containing only
1348         *         the new value.
1349         */
1350        // TODO: move to Helper as static method
1351        @NonNull
1352        private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
1353            if (set == null) {
1354                set = new ArraySet<>(1);
1355            }
1356
1357            set.add(valueToAdd);
1358
1359            return set;
1360        }
1361
1362        /**
1363         * Remove a value from a set.
1364         *
1365         * @param set           The set or null (== empty set)
1366         * @param valueToRemove The value to remove
1367         *
1368         * @return The set without the removed value. {@code null} if set was null, or is empty
1369         *         after removal.
1370         */
1371        // TODO: move to Helper as static method
1372        @Nullable
1373        private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
1374            if (set == null) {
1375                return null;
1376            }
1377
1378            set.remove(valueToRemove);
1379
1380            if (set.isEmpty()) {
1381                return null;
1382            }
1383
1384            return set;
1385        }
1386
1387        /**
1388         * Set the tracked views.
1389         *
1390         * @param trackedIds The views to be tracked
1391         */
1392        TrackedViews(@Nullable AutofillId[] trackedIds) {
1393            final AutofillClient client = getClientLocked();
1394            if (trackedIds != null && client != null) {
1395                final boolean[] isVisible;
1396
1397                if (client.isVisibleForAutofill()) {
1398                    isVisible = client.getViewVisibility(getViewIds(trackedIds));
1399                } else {
1400                    // All false
1401                    isVisible = new boolean[trackedIds.length];
1402                }
1403
1404                final int numIds = trackedIds.length;
1405                for (int i = 0; i < numIds; i++) {
1406                    final AutofillId id = trackedIds[i];
1407
1408                    if (isVisible[i]) {
1409                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1410                    } else {
1411                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1412                    }
1413                }
1414            }
1415
1416            if (sVerbose) {
1417                Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
1418                        + " mVisibleTrackedIds=" + mVisibleTrackedIds
1419                        + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
1420            }
1421
1422            if (mVisibleTrackedIds == null) {
1423                finishSessionLocked();
1424            }
1425        }
1426
1427        /**
1428         * Called when a {@link View view's} visibility changes.
1429         *
1430         * @param view {@link View} that was exited.
1431         * @param isVisible visible if the view is visible in the view hierarchy.
1432         */
1433        void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
1434            AutofillId id = getAutofillId(view);
1435            AutofillClient client = getClientLocked();
1436
1437            if (sDebug) {
1438                Log.d(TAG, "notifyViewVisibilityChange(): id=" + id + " isVisible="
1439                        + isVisible);
1440            }
1441
1442            if (client != null && client.isVisibleForAutofill()) {
1443                if (isVisible) {
1444                    if (isInSet(mInvisibleTrackedIds, id)) {
1445                        mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
1446                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1447                    }
1448                } else {
1449                    if (isInSet(mVisibleTrackedIds, id)) {
1450                        mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
1451                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1452                    }
1453                }
1454            }
1455
1456            if (mVisibleTrackedIds == null) {
1457                if (sVerbose) {
1458                    Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds);
1459                }
1460                finishSessionLocked();
1461            }
1462        }
1463
1464        /**
1465         * Called once the client becomes visible.
1466         *
1467         * @see AutofillClient#isVisibleForAutofill()
1468         */
1469        void onVisibleForAutofillLocked() {
1470            // The visibility of the views might have changed while the client was not be visible,
1471            // hence update the visibility state for all views.
1472            AutofillClient client = getClientLocked();
1473            ArraySet<AutofillId> updatedVisibleTrackedIds = null;
1474            ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
1475            if (client != null) {
1476                if (mInvisibleTrackedIds != null) {
1477                    final ArrayList<AutofillId> orderedInvisibleIds =
1478                            new ArrayList<>(mInvisibleTrackedIds);
1479                    final boolean[] isVisible = client.getViewVisibility(
1480                            getViewIds(orderedInvisibleIds));
1481
1482                    final int numInvisibleTrackedIds = orderedInvisibleIds.size();
1483                    for (int i = 0; i < numInvisibleTrackedIds; i++) {
1484                        final AutofillId id = orderedInvisibleIds.get(i);
1485                        if (isVisible[i]) {
1486                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1487
1488                            if (sDebug) {
1489                                Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
1490                            }
1491                        } else {
1492                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1493                        }
1494                    }
1495                }
1496
1497                if (mVisibleTrackedIds != null) {
1498                    final ArrayList<AutofillId> orderedVisibleIds =
1499                            new ArrayList<>(mVisibleTrackedIds);
1500                    final boolean[] isVisible = client.getViewVisibility(
1501                            getViewIds(orderedVisibleIds));
1502
1503                    final int numVisibleTrackedIds = orderedVisibleIds.size();
1504                    for (int i = 0; i < numVisibleTrackedIds; i++) {
1505                        final AutofillId id = orderedVisibleIds.get(i);
1506
1507                        if (isVisible[i]) {
1508                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1509                        } else {
1510                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1511
1512                            if (sDebug) {
1513                                Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
1514                            }
1515                        }
1516                    }
1517                }
1518
1519                mInvisibleTrackedIds = updatedInvisibleTrackedIds;
1520                mVisibleTrackedIds = updatedVisibleTrackedIds;
1521            }
1522
1523            if (mVisibleTrackedIds == null) {
1524                finishSessionLocked();
1525            }
1526        }
1527    }
1528
1529    /**
1530     * Callback for autofill related events.
1531     *
1532     * <p>Typically used for applications that display their own "auto-complete" views, so they can
1533     * enable / disable such views when the autofill UI affordance is shown / hidden.
1534     */
1535    public abstract static class AutofillCallback {
1536
1537        /** @hide */
1538        @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
1539        @Retention(RetentionPolicy.SOURCE)
1540        public @interface AutofillEventType {}
1541
1542        /**
1543         * The autofill input UI affordance associated with the view was shown.
1544         *
1545         * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
1546         * should be hidden upon receiving this event.
1547         */
1548        public static final int EVENT_INPUT_SHOWN = 1;
1549
1550        /**
1551         * The autofill input UI affordance associated with the view was hidden.
1552         *
1553         * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
1554         * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
1555         */
1556        public static final int EVENT_INPUT_HIDDEN = 2;
1557
1558        /**
1559         * The autofill input UI affordance associated with the view isn't shown because
1560         * autofill is not available.
1561         *
1562         * <p>If the view provides its own auto-complete UI affordance but was not displaying it
1563         * to avoid flickering, it could shown it upon receiving this event.
1564         */
1565        public static final int EVENT_INPUT_UNAVAILABLE = 3;
1566
1567        /**
1568         * Called after a change in the autofill state associated with a view.
1569         *
1570         * @param view view associated with the change.
1571         *
1572         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1573         */
1574        public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
1575        }
1576
1577        /**
1578         * Called after a change in the autofill state associated with a virtual view.
1579         *
1580         * @param view parent view associated with the change.
1581         * @param virtualId id identifying the virtual child inside the parent view.
1582         *
1583         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1584         */
1585        public void onAutofillEvent(@NonNull View view, int virtualId,
1586                @AutofillEventType int event) {
1587        }
1588    }
1589
1590    private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
1591        private final WeakReference<AutofillManager> mAfm;
1592
1593        AutofillManagerClient(AutofillManager autofillManager) {
1594            mAfm = new WeakReference<>(autofillManager);
1595        }
1596
1597        @Override
1598        public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
1599            final AutofillManager afm = mAfm.get();
1600            if (afm != null) {
1601                afm.post(() -> afm.setState(enabled, resetSession, resetClient));
1602            }
1603        }
1604
1605        @Override
1606        public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
1607            final AutofillManager afm = mAfm.get();
1608            if (afm != null) {
1609                afm.post(() -> afm.autofill(sessionId, ids, values));
1610            }
1611        }
1612
1613        @Override
1614        public void authenticate(int sessionId, int authenticationId, IntentSender intent,
1615                Intent fillInIntent) {
1616            final AutofillManager afm = mAfm.get();
1617            if (afm != null) {
1618                afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
1619            }
1620        }
1621
1622        @Override
1623        public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1624                Rect anchorBounds, IAutofillWindowPresenter presenter) {
1625            final AutofillManager afm = mAfm.get();
1626            if (afm != null) {
1627                afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
1628                        presenter));
1629            }
1630        }
1631
1632        @Override
1633        public void requestHideFillUi(int sessionId, AutofillId id) {
1634            final AutofillManager afm = mAfm.get();
1635            if (afm != null) {
1636                afm.post(() -> afm.requestHideFillUi(id));
1637            }
1638        }
1639
1640        @Override
1641        public void notifyNoFillUi(int sessionId, AutofillId id) {
1642            final AutofillManager afm = mAfm.get();
1643            if (afm != null) {
1644                afm.post(() -> afm.notifyNoFillUi(sessionId, id));
1645            }
1646        }
1647
1648        @Override
1649        public void startIntentSender(IntentSender intentSender) {
1650            final AutofillManager afm = mAfm.get();
1651            if (afm != null) {
1652                afm.post(() -> {
1653                    try {
1654                        afm.mContext.startIntentSender(intentSender, null, 0, 0, 0);
1655                    } catch (IntentSender.SendIntentException e) {
1656                        Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
1657                    }
1658                });
1659            }
1660        }
1661
1662        @Override
1663        public void setTrackedViews(int sessionId, AutofillId[] ids,
1664                boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
1665            final AutofillManager afm = mAfm.get();
1666            if (afm != null) {
1667                afm.post(() ->
1668                        afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
1669                );
1670            }
1671        }
1672    }
1673}
1674