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