AutofillManager.java revision a937238eaeac6c3577af0c14ceca822890cc979b
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/**
57 * The {@link AutofillManager} provides ways for apps and custom views to integrate with the
58 * Autofill Framework lifecycle.
59 *
60 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an
61 * activity context; the autofill context is created when one of the following methods is called for
62 * the first time in an activity context, and the current user has an enabled autofill service:
63 *
64 * <ul>
65 *   <li>{@link #notifyViewEntered(View)}
66 *   <li>{@link #notifyViewEntered(View, int, Rect)}
67 *   <li>{@link #requestAutofill(View)}
68 * </ul>
69 *
70 * <p>Tipically, the context is automatically created when the first view of the activity is
71 * focused because {@code View.onFocusChanged()} indirectly calls
72 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to
73 * explicitly create it (for example, a custom view developer could offer a contextual menu action
74 * in a text-field view to let users manually request autofill).
75 *
76 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure}
77 * that represents the view hierarchy by calling
78 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views
79 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in
80 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and
81 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in
82 * the hierarchy.
83 *
84 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which
85 * parses it looking for views that can be autofilled. If the service finds such views, it returns
86 * a data structure to the Android System containing the following optional info:
87 *
88 * <ul>
89 *   <li>Datasets used to autofill subsets of views in the activity.
90 *   <li>Id of views that the service can save their values for future autofilling.
91 * </ul>
92 *
93 * <p>When the service returns datasets, the Android System displays an autofill dataset picker
94 * UI affordance associated with the view, when the view is focused on and is part of a dataset.
95 * The application can be notified when the affordance is shown by registering an
96 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
97 * selects a dataset from the affordance, all views present in the dataset are autofilled, through
98 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
99 *
100 * <p>When the service returns ids of savable views, the Android System keeps track of changes
101 * made to these views, so they can be used to determine if the autofill save UI is shown later.
102 *
103 * <p>The context is then finished when one of the following occurs:
104 *
105 * <ul>
106 *   <li>{@link #commit()} is called or all savable views are gone.
107 *   <li>{@link #cancel()} is called.
108 * </ul>
109 *
110 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
111 * shows a save UI affordance if the value of savable views have changed. If the user selects the
112 * option to Save, the current value of the views is then sent to the autofill service.
113 *
114 * <p>It is safe to call into its methods from any thread.
115 */
116@SystemService(Context.AUTOFILL_MANAGER_SERVICE)
117public final class AutofillManager {
118
119    private static final String TAG = "AutofillManager";
120
121    /**
122     * Intent extra: The assist structure which captures the filled screen.
123     *
124     * <p>
125     * Type: {@link android.app.assist.AssistStructure}
126     */
127    public static final String EXTRA_ASSIST_STRUCTURE =
128            "android.view.autofill.extra.ASSIST_STRUCTURE";
129
130    /**
131     * Intent extra: The result of an authentication operation. It is
132     * either a fully populated {@link android.service.autofill.FillResponse}
133     * or a fully populated {@link android.service.autofill.Dataset} if
134     * a response or a dataset is being authenticated respectively.
135     *
136     * <p>
137     * Type: {@link android.service.autofill.FillResponse} or a
138     * {@link android.service.autofill.Dataset}
139     */
140    public static final String EXTRA_AUTHENTICATION_RESULT =
141            "android.view.autofill.extra.AUTHENTICATION_RESULT";
142
143    /**
144     * Intent extra: The optional extras provided by the
145     * {@link android.service.autofill.AutofillService}.
146     *
147     * <p>For example, when the service responds to a {@link
148     * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
149     * a {@code FillResponse} that requires authentication, the Intent that launches the
150     * service authentication will contain the Bundle set by
151     * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
152     *
153     * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
154     * can also add this bundle to the {@link Intent} set as the
155     * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
156     * so the bundle can be recovered later on
157     * {@link android.service.autofill.SaveRequest#getClientState()}.
158     *
159     * <p>
160     * Type: {@link android.os.Bundle}
161     */
162    public static final String EXTRA_CLIENT_STATE =
163            "android.view.autofill.extra.CLIENT_STATE";
164
165
166    /** @hide */
167    public static final String EXTRA_RESTORE_SESSION_TOKEN =
168            "android.view.autofill.extra.RESTORE_SESSION_TOKEN";
169
170    private static final String SESSION_ID_TAG = "android:sessionId";
171    private static final String STATE_TAG = "android:state";
172    private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
173
174
175    /** @hide */ public static final int ACTION_START_SESSION = 1;
176    /** @hide */ public static final int ACTION_VIEW_ENTERED =  2;
177    /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
178    /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
179
180
181    /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
182    /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
183    /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
184
185    /** Which bits in an authentication id are used for the dataset id */
186    private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
187    /** How many bits in an authentication id are used for the dataset id */
188    private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
189    /** @hide The index for an undefined data set */
190    public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
191
192    /**
193     * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI.
194     *
195     * @hide
196     */
197    public static final int PENDING_UI_OPERATION_CANCEL = 1;
198
199    /**
200     * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI.
201     *
202     * @hide
203     */
204    public static final int PENDING_UI_OPERATION_RESTORE = 2;
205
206    /**
207     * Initial state of the autofill context, set when there is no session (i.e., when
208     * {@link #mSessionId} is {@link #NO_SESSION}).
209     *
210     * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to
211     * the server.
212     *
213     * @hide
214     */
215    public static final int STATE_UNKNOWN = 0;
216
217    /**
218     * State where the autofill context hasn't been {@link #commit() finished} nor
219     * {@link #cancel() canceled} yet.
220     *
221     * @hide
222     */
223    public static final int STATE_ACTIVE = 1;
224
225    /**
226     * State where the autofill context was finished by the server because the autofill
227     * service could not autofill the page.
228     *
229     * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
230     * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
231     *
232     * @hide
233     */
234    public static final int STATE_FINISHED = 2;
235
236    /**
237     * State where the autofill context has been {@link #commit() finished} but the server still has
238     * a session because the Save UI hasn't been dismissed yet.
239     *
240     * @hide
241     */
242    public static final int STATE_SHOWING_SAVE_UI = 3;
243
244    /**
245     * Makes an authentication id from a request id and a dataset id.
246     *
247     * @param requestId The request id.
248     * @param datasetId The dataset id.
249     * @return The authentication id.
250     * @hide
251     */
252    public static int makeAuthenticationId(int requestId, int datasetId) {
253        return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT)
254                | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK);
255    }
256
257    /**
258     * Gets the request id from an authentication id.
259     *
260     * @param authRequestId The authentication id.
261     * @return The request id.
262     * @hide
263     */
264    public static int getRequestIdFromAuthenticationId(int authRequestId) {
265        return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT);
266    }
267
268    /**
269     * Gets the dataset id from an authentication id.
270     *
271     * @param authRequestId The authentication id.
272     * @return The dataset id.
273     * @hide
274     */
275    public static int getDatasetIdFromAuthenticationId(int authRequestId) {
276        return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK);
277    }
278
279    private final MetricsLogger mMetricsLogger = new MetricsLogger();
280
281    /**
282     * There is currently no session running.
283     * {@hide}
284     */
285    public static final int NO_SESSION = Integer.MIN_VALUE;
286
287    private final IAutoFillManager mService;
288
289    private final Object mLock = new Object();
290
291    @GuardedBy("mLock")
292    private IAutoFillManagerClient mServiceClient;
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            final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
969            if (newClientState != null) {
970                responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
971            }
972            try {
973                mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
974                        mContext.getUserId());
975            } catch (RemoteException e) {
976                Log.e(TAG, "Error delivering authentication result", e);
977            }
978        }
979    }
980
981    private static AutofillId getAutofillId(View view) {
982        return new AutofillId(view.getAutofillViewId());
983    }
984
985    private static AutofillId getAutofillId(View parent, int virtualId) {
986        return new AutofillId(parent.getAutofillViewId(), virtualId);
987    }
988
989    private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
990            @NonNull AutofillValue value, int flags) {
991        if (sVerbose) {
992            Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
993                    + ", flags=" + flags + ", state=" + getStateAsStringLocked());
994        }
995        if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
996            if (sVerbose) {
997                Log.v(TAG, "not automatically starting session for " + id
998                        + " on state " + getStateAsStringLocked());
999            }
1000            return;
1001        }
1002        try {
1003            mSessionId = mService.startSession(mContext.getActivityToken(),
1004                    mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
1005                    mCallback != null, flags, mContext.getOpPackageName());
1006            if (mSessionId != NO_SESSION) {
1007                mState = STATE_ACTIVE;
1008            }
1009            final AutofillClient client = getClientLocked();
1010            if (client != null) {
1011                client.autofillCallbackResetableStateAvailable();
1012            }
1013        } catch (RemoteException e) {
1014            throw e.rethrowFromSystemServer();
1015        }
1016    }
1017
1018    private void finishSessionLocked() {
1019        if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
1020
1021        if (!isActiveLocked()) return;
1022
1023        try {
1024            mService.finishSession(mSessionId, mContext.getUserId());
1025        } catch (RemoteException e) {
1026            throw e.rethrowFromSystemServer();
1027        }
1028
1029        resetSessionLocked();
1030    }
1031
1032    private void cancelSessionLocked() {
1033        if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
1034
1035        if (!isActiveLocked()) return;
1036
1037        try {
1038            mService.cancelSession(mSessionId, mContext.getUserId());
1039        } catch (RemoteException e) {
1040            throw e.rethrowFromSystemServer();
1041        }
1042
1043        resetSessionLocked();
1044    }
1045
1046    private void resetSessionLocked() {
1047        mSessionId = NO_SESSION;
1048        mState = STATE_UNKNOWN;
1049        mTrackedViews = null;
1050        mFillableIds = null;
1051    }
1052
1053    private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
1054            int flags) {
1055        if (sVerbose && action != ACTION_VIEW_EXITED) {
1056            Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
1057                    + ", value=" + value + ", action=" + action + ", flags=" + flags);
1058        }
1059        boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0;
1060
1061        try {
1062            if (restartIfNecessary) {
1063                final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
1064                        mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
1065                        mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
1066                if (newId != mSessionId) {
1067                    if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
1068                    mSessionId = newId;
1069                    mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
1070                    final AutofillClient client = getClientLocked();
1071                    if (client != null) {
1072                        client.autofillCallbackResetableStateAvailable();
1073                    }
1074                }
1075            } else {
1076                mService.updateSession(mSessionId, id, bounds, value, action, flags,
1077                        mContext.getUserId());
1078            }
1079
1080        } catch (RemoteException e) {
1081            throw e.rethrowFromSystemServer();
1082        }
1083    }
1084
1085    private void ensureServiceClientAddedIfNeededLocked() {
1086        if (getClientLocked() == null) {
1087            return;
1088        }
1089
1090        if (mServiceClient == null) {
1091            mServiceClient = new AutofillManagerClient(this);
1092            try {
1093                final int flags = mService.addClient(mServiceClient, mContext.getUserId());
1094                mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
1095                sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
1096                sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
1097            } catch (RemoteException e) {
1098                throw e.rethrowFromSystemServer();
1099            }
1100        }
1101    }
1102
1103    /**
1104     * Registers a {@link AutofillCallback} to receive autofill events.
1105     *
1106     * @param callback callback to receive events.
1107     */
1108    public void registerCallback(@Nullable AutofillCallback callback) {
1109        if (!hasAutofillFeature()) {
1110            return;
1111        }
1112        synchronized (mLock) {
1113            if (callback == null) return;
1114
1115            final boolean hadCallback = mCallback != null;
1116            mCallback = callback;
1117
1118            if (!hadCallback) {
1119                try {
1120                    mService.setHasCallback(mSessionId, mContext.getUserId(), true);
1121                } catch (RemoteException e) {
1122                    throw e.rethrowFromSystemServer();
1123                }
1124            }
1125        }
1126    }
1127
1128    /**
1129     * Unregisters a {@link AutofillCallback} to receive autofill events.
1130     *
1131     * @param callback callback to stop receiving events.
1132     */
1133    public void unregisterCallback(@Nullable AutofillCallback callback) {
1134        if (!hasAutofillFeature()) {
1135            return;
1136        }
1137        synchronized (mLock) {
1138            if (callback == null || mCallback == null || callback != mCallback) return;
1139
1140            mCallback = null;
1141
1142            try {
1143                mService.setHasCallback(mSessionId, mContext.getUserId(), false);
1144            } catch (RemoteException e) {
1145                throw e.rethrowFromSystemServer();
1146            }
1147        }
1148    }
1149
1150    private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1151            Rect anchorBounds, IAutofillWindowPresenter presenter) {
1152        final View anchor = findView(id);
1153        if (anchor == null) {
1154            return;
1155        }
1156
1157        AutofillCallback callback = null;
1158        synchronized (mLock) {
1159            if (mSessionId == sessionId) {
1160                AutofillClient client = getClientLocked();
1161
1162                if (client != null) {
1163                    if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
1164                            anchorBounds, presenter) && mCallback != null) {
1165                        callback = mCallback;
1166                    }
1167                }
1168            }
1169        }
1170
1171        if (callback != null) {
1172            if (id.isVirtual()) {
1173                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1174                        AutofillCallback.EVENT_INPUT_SHOWN);
1175            } else {
1176                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
1177            }
1178        }
1179    }
1180
1181    private void authenticate(int sessionId, int authenticationId, IntentSender intent,
1182            Intent fillInIntent) {
1183        synchronized (mLock) {
1184            if (sessionId == mSessionId) {
1185                AutofillClient client = getClientLocked();
1186                if (client != null) {
1187                    client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent);
1188                }
1189            }
1190        }
1191    }
1192
1193    private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
1194        synchronized (mLock) {
1195            mEnabled = enabled;
1196            if (!mEnabled || resetSession) {
1197                // Reset the session state
1198                resetSessionLocked();
1199            }
1200            if (resetClient) {
1201                // Reset connection to system
1202                mServiceClient = null;
1203            }
1204        }
1205    }
1206
1207    /**
1208     * Sets a view as autofilled if the current value is the {code targetValue}.
1209     *
1210     * @param view The view that is to be autofilled
1211     * @param targetValue The value we want to fill into view
1212     */
1213    private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
1214        AutofillValue currentValue = view.getAutofillValue();
1215        if (Objects.equals(currentValue, targetValue)) {
1216            synchronized (mLock) {
1217                if (mLastAutofilledData == null) {
1218                    mLastAutofilledData = new ParcelableMap(1);
1219                }
1220                mLastAutofilledData.put(getAutofillId(view), targetValue);
1221            }
1222            view.setAutofilled(true);
1223        }
1224    }
1225
1226    private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
1227        synchronized (mLock) {
1228            if (sessionId != mSessionId) {
1229                return;
1230            }
1231
1232            final AutofillClient client = getClientLocked();
1233            if (client == null) {
1234                return;
1235            }
1236
1237            final int itemCount = ids.size();
1238            int numApplied = 0;
1239            ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
1240            final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
1241
1242            for (int i = 0; i < itemCount; i++) {
1243                final AutofillId id = ids.get(i);
1244                final AutofillValue value = values.get(i);
1245                final int viewId = id.getViewId();
1246                final View view = views[i];
1247                if (view == null) {
1248                    Log.w(TAG, "autofill(): no View with id " + viewId);
1249                    continue;
1250                }
1251                if (id.isVirtual()) {
1252                    if (virtualValues == null) {
1253                        // Most likely there will be just one view with virtual children.
1254                        virtualValues = new ArrayMap<>(1);
1255                    }
1256                    SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
1257                    if (valuesByParent == null) {
1258                        // We don't know the size yet, but usually it will be just a few fields...
1259                        valuesByParent = new SparseArray<>(5);
1260                        virtualValues.put(view, valuesByParent);
1261                    }
1262                    valuesByParent.put(id.getVirtualChildId(), value);
1263                } else {
1264                    // Mark the view as to be autofilled with 'value'
1265                    if (mLastAutofilledData == null) {
1266                        mLastAutofilledData = new ParcelableMap(itemCount - i);
1267                    }
1268                    mLastAutofilledData.put(id, value);
1269
1270                    view.autofill(value);
1271
1272                    // Set as autofilled if the values match now, e.g. when the value was updated
1273                    // synchronously.
1274                    // If autofill happens async, the view is set to autofilled in
1275                    // notifyValueChanged.
1276                    setAutofilledIfValuesIs(view, value);
1277
1278                    numApplied++;
1279                }
1280            }
1281
1282            if (virtualValues != null) {
1283                for (int i = 0; i < virtualValues.size(); i++) {
1284                    final View parent = virtualValues.keyAt(i);
1285                    final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
1286                    parent.autofill(childrenValues);
1287                    numApplied += childrenValues.size();
1288                }
1289            }
1290
1291            final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
1292                    .setPackageName(mContext.getPackageName())
1293                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
1294                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
1295            mMetricsLogger.write(log);
1296        }
1297    }
1298
1299    /**
1300     *  Set the tracked views.
1301     *
1302     * @param trackedIds The views to be tracked
1303     * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
1304     * @param fillableIds Views that might anchor FillUI.
1305     */
1306    private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
1307            boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
1308        synchronized (mLock) {
1309            if (mEnabled && mSessionId == sessionId) {
1310                if (saveOnAllViewsInvisible) {
1311                    mTrackedViews = new TrackedViews(trackedIds);
1312                } else {
1313                    mTrackedViews = null;
1314                }
1315                if (fillableIds != null) {
1316                    if (mFillableIds == null) {
1317                        mFillableIds = new ArraySet<>(fillableIds.length);
1318                    }
1319                    for (AutofillId id : fillableIds) {
1320                        mFillableIds.add(id);
1321                    }
1322                    if (sVerbose) {
1323                        Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
1324                                + ", mFillableIds" + mFillableIds);
1325                    }
1326                }
1327            }
1328        }
1329    }
1330
1331    private void setSaveUiState(int sessionId, boolean shown) {
1332        if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
1333        synchronized (mLock) {
1334            if (mSessionId != NO_SESSION) {
1335                // Race condition: app triggered a new session after the previous session was
1336                // finished but before server called setSaveUiState() - need to cancel the new
1337                // session to avoid further inconsistent behavior.
1338                Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown
1339                        + ") called on existing session " + mSessionId + "; cancelling it");
1340                cancelSessionLocked();
1341            }
1342            if (shown) {
1343                mSessionId = sessionId;
1344                mState = STATE_SHOWING_SAVE_UI;
1345            } else {
1346                mSessionId = NO_SESSION;
1347                mState = STATE_UNKNOWN;
1348            }
1349        }
1350    }
1351
1352    /**
1353     * Marks the state of the session as finished.
1354     *
1355     * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
1356     *  FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
1357     */
1358    private void setSessionFinished(int newState) {
1359        synchronized (mLock) {
1360            if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState);
1361            resetSessionLocked();
1362            mState = newState;
1363        }
1364    }
1365
1366    private void requestHideFillUi(AutofillId id) {
1367        final View anchor = findView(id);
1368        if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
1369        if (anchor == null) {
1370            return;
1371        }
1372        requestHideFillUi(id, anchor);
1373    }
1374
1375    private void requestHideFillUi(AutofillId id, View anchor) {
1376
1377        AutofillCallback callback = null;
1378        synchronized (mLock) {
1379            // We do not check the session id for two reasons:
1380            // 1. If local and remote session id are off sync the UI would be stuck shown
1381            // 2. There is a race between the user state being destroyed due the fill
1382            //    service being uninstalled and the UI being dismissed.
1383            AutofillClient client = getClientLocked();
1384            if (client != null) {
1385                if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
1386                    callback = mCallback;
1387                }
1388            }
1389        }
1390
1391        if (callback != null) {
1392            if (id.isVirtual()) {
1393                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1394                        AutofillCallback.EVENT_INPUT_HIDDEN);
1395            } else {
1396                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
1397            }
1398        }
1399    }
1400
1401    private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
1402        if (sVerbose) {
1403            Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
1404                    + ", finished=" + sessionFinished);
1405        }
1406        final View anchor = findView(id);
1407        if (anchor == null) {
1408            return;
1409        }
1410
1411        AutofillCallback callback = null;
1412        synchronized (mLock) {
1413            if (mSessionId == sessionId && getClientLocked() != null) {
1414                callback = mCallback;
1415            }
1416        }
1417
1418        if (callback != null) {
1419            if (id.isVirtual()) {
1420                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
1421                        AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1422            } else {
1423                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1424            }
1425        }
1426
1427        if (sessionFinished) {
1428            // Callback call was "hijacked" to also update the session state.
1429            setSessionFinished(STATE_FINISHED);
1430        }
1431    }
1432
1433    /**
1434     * Get an array of viewIds from a List of {@link AutofillId}.
1435     *
1436     * @param autofillIds The autofill ids to convert
1437     *
1438     * @return The array of viewIds.
1439     */
1440    // TODO: move to Helper as static method
1441    @NonNull private int[] getViewIds(@NonNull AutofillId[] autofillIds) {
1442        final int numIds = autofillIds.length;
1443        final int[] viewIds = new int[numIds];
1444        for (int i = 0; i < numIds; i++) {
1445            viewIds[i] = autofillIds[i].getViewId();
1446        }
1447
1448        return viewIds;
1449    }
1450
1451    // TODO: move to Helper as static method
1452    @NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) {
1453        final int numIds = autofillIds.size();
1454        final int[] viewIds = new int[numIds];
1455        for (int i = 0; i < numIds; i++) {
1456            viewIds[i] = autofillIds.get(i).getViewId();
1457        }
1458
1459        return viewIds;
1460    }
1461
1462    /**
1463     * Find a single view by its id.
1464     *
1465     * @param autofillId The autofill id of the view
1466     *
1467     * @return The view or {@code null} if view was not found
1468     */
1469    private View findView(@NonNull AutofillId autofillId) {
1470        final AutofillClient client = getClientLocked();
1471
1472        if (client == null) {
1473            return null;
1474        }
1475
1476        return client.findViewByAutofillIdTraversal(autofillId.getViewId());
1477    }
1478
1479    /** @hide */
1480    public boolean hasAutofillFeature() {
1481        return mService != null;
1482    }
1483
1484    /** @hide */
1485    public void onPendingSaveUi(int operation, IBinder token) {
1486        if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
1487
1488        synchronized (mLock) {
1489            try {
1490                mService.onPendingSaveUi(operation, token);
1491            } catch (RemoteException e) {
1492                e.rethrowFromSystemServer();
1493            }
1494        }
1495    }
1496
1497    /** @hide */
1498    public void dump(String outerPrefix, PrintWriter pw) {
1499        pw.print(outerPrefix); pw.println("AutofillManager:");
1500        final String pfx = outerPrefix + "  ";
1501        pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
1502        pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
1503        pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
1504        pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
1505        pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
1506        pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData);
1507        pw.print(pfx); pw.print("tracked views: ");
1508        if (mTrackedViews == null) {
1509            pw.println("null");
1510        } else {
1511            final String pfx2 = pfx + "  ";
1512            pw.println();
1513            pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds);
1514            pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
1515        }
1516        pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
1517    }
1518
1519    private String getStateAsStringLocked() {
1520        switch (mState) {
1521            case STATE_UNKNOWN:
1522                return "STATE_UNKNOWN";
1523            case STATE_ACTIVE:
1524                return "STATE_ACTIVE";
1525            case STATE_FINISHED:
1526                return "STATE_FINISHED";
1527            case STATE_SHOWING_SAVE_UI:
1528                return "STATE_SHOWING_SAVE_UI";
1529            default:
1530                return "INVALID:" + mState;
1531        }
1532    }
1533
1534    private boolean isActiveLocked() {
1535        return mState == STATE_ACTIVE;
1536    }
1537
1538    private boolean isFinishedLocked() {
1539        return mState == STATE_FINISHED;
1540    }
1541
1542    private void post(Runnable runnable) {
1543        final AutofillClient client = getClientLocked();
1544        if (client == null) {
1545            if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
1546            return;
1547        }
1548        client.runOnUiThread(runnable);
1549    }
1550
1551    /**
1552     * View tracking information. Once all tracked views become invisible the session is finished.
1553     */
1554    private class TrackedViews {
1555        /** Visible tracked views */
1556        @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
1557
1558        /** Invisible tracked views */
1559        @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
1560
1561        /**
1562         * Check if set is null or value is in set.
1563         *
1564         * @param set   The set or null (== empty set)
1565         * @param value The value that might be in the set
1566         *
1567         * @return {@code true} iff set is not empty and value is in set
1568         */
1569        // TODO: move to Helper as static method
1570        private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
1571            return set != null && set.contains(value);
1572        }
1573
1574        /**
1575         * Add a value to a set. If set is null, create a new set.
1576         *
1577         * @param set        The set or null (== empty set)
1578         * @param valueToAdd The value to add
1579         *
1580         * @return The set including the new value. If set was {@code null}, a set containing only
1581         *         the new value.
1582         */
1583        // TODO: move to Helper as static method
1584        @NonNull
1585        private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
1586            if (set == null) {
1587                set = new ArraySet<>(1);
1588            }
1589
1590            set.add(valueToAdd);
1591
1592            return set;
1593        }
1594
1595        /**
1596         * Remove a value from a set.
1597         *
1598         * @param set           The set or null (== empty set)
1599         * @param valueToRemove The value to remove
1600         *
1601         * @return The set without the removed value. {@code null} if set was null, or is empty
1602         *         after removal.
1603         */
1604        // TODO: move to Helper as static method
1605        @Nullable
1606        private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
1607            if (set == null) {
1608                return null;
1609            }
1610
1611            set.remove(valueToRemove);
1612
1613            if (set.isEmpty()) {
1614                return null;
1615            }
1616
1617            return set;
1618        }
1619
1620        /**
1621         * Set the tracked views.
1622         *
1623         * @param trackedIds The views to be tracked
1624         */
1625        TrackedViews(@Nullable AutofillId[] trackedIds) {
1626            final AutofillClient client = getClientLocked();
1627            if (trackedIds != null && client != null) {
1628                final boolean[] isVisible;
1629
1630                if (client.isVisibleForAutofill()) {
1631                    isVisible = client.getViewVisibility(getViewIds(trackedIds));
1632                } else {
1633                    // All false
1634                    isVisible = new boolean[trackedIds.length];
1635                }
1636
1637                final int numIds = trackedIds.length;
1638                for (int i = 0; i < numIds; i++) {
1639                    final AutofillId id = trackedIds[i];
1640
1641                    if (isVisible[i]) {
1642                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1643                    } else {
1644                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1645                    }
1646                }
1647            }
1648
1649            if (sVerbose) {
1650                Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
1651                        + " mVisibleTrackedIds=" + mVisibleTrackedIds
1652                        + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
1653            }
1654
1655            if (mVisibleTrackedIds == null) {
1656                finishSessionLocked();
1657            }
1658        }
1659
1660        /**
1661         * Called when a {@link View view's} visibility changes.
1662         *
1663         * @param id the id of the view/virtual view whose visibility changed.
1664         * @param isVisible visible if the view is visible in the view hierarchy.
1665         */
1666        void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) {
1667            AutofillClient client = getClientLocked();
1668
1669            if (sDebug) {
1670                Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible="
1671                        + isVisible);
1672            }
1673
1674            if (client != null && client.isVisibleForAutofill()) {
1675                if (isVisible) {
1676                    if (isInSet(mInvisibleTrackedIds, id)) {
1677                        mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
1678                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1679                    }
1680                } else {
1681                    if (isInSet(mVisibleTrackedIds, id)) {
1682                        mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
1683                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1684                    }
1685                }
1686            }
1687
1688            if (mVisibleTrackedIds == null) {
1689                if (sVerbose) {
1690                    Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds);
1691                }
1692                finishSessionLocked();
1693            }
1694        }
1695
1696        /**
1697         * Called once the client becomes visible.
1698         *
1699         * @see AutofillClient#isVisibleForAutofill()
1700         */
1701        void onVisibleForAutofillLocked() {
1702            // The visibility of the views might have changed while the client was not be visible,
1703            // hence update the visibility state for all views.
1704            AutofillClient client = getClientLocked();
1705            ArraySet<AutofillId> updatedVisibleTrackedIds = null;
1706            ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
1707            if (client != null) {
1708                if (mInvisibleTrackedIds != null) {
1709                    final ArrayList<AutofillId> orderedInvisibleIds =
1710                            new ArrayList<>(mInvisibleTrackedIds);
1711                    final boolean[] isVisible = client.getViewVisibility(
1712                            getViewIds(orderedInvisibleIds));
1713
1714                    final int numInvisibleTrackedIds = orderedInvisibleIds.size();
1715                    for (int i = 0; i < numInvisibleTrackedIds; i++) {
1716                        final AutofillId id = orderedInvisibleIds.get(i);
1717                        if (isVisible[i]) {
1718                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1719
1720                            if (sDebug) {
1721                                Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
1722                            }
1723                        } else {
1724                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1725                        }
1726                    }
1727                }
1728
1729                if (mVisibleTrackedIds != null) {
1730                    final ArrayList<AutofillId> orderedVisibleIds =
1731                            new ArrayList<>(mVisibleTrackedIds);
1732                    final boolean[] isVisible = client.getViewVisibility(
1733                            getViewIds(orderedVisibleIds));
1734
1735                    final int numVisibleTrackedIds = orderedVisibleIds.size();
1736                    for (int i = 0; i < numVisibleTrackedIds; i++) {
1737                        final AutofillId id = orderedVisibleIds.get(i);
1738
1739                        if (isVisible[i]) {
1740                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1741                        } else {
1742                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1743
1744                            if (sDebug) {
1745                                Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
1746                            }
1747                        }
1748                    }
1749                }
1750
1751                mInvisibleTrackedIds = updatedInvisibleTrackedIds;
1752                mVisibleTrackedIds = updatedVisibleTrackedIds;
1753            }
1754
1755            if (mVisibleTrackedIds == null) {
1756                finishSessionLocked();
1757            }
1758        }
1759    }
1760
1761    /**
1762     * Callback for autofill related events.
1763     *
1764     * <p>Typically used for applications that display their own "auto-complete" views, so they can
1765     * enable / disable such views when the autofill UI affordance is shown / hidden.
1766     */
1767    public abstract static class AutofillCallback {
1768
1769        /** @hide */
1770        @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
1771        @Retention(RetentionPolicy.SOURCE)
1772        public @interface AutofillEventType {}
1773
1774        /**
1775         * The autofill input UI affordance associated with the view was shown.
1776         *
1777         * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
1778         * should be hidden upon receiving this event.
1779         */
1780        public static final int EVENT_INPUT_SHOWN = 1;
1781
1782        /**
1783         * The autofill input UI affordance associated with the view was hidden.
1784         *
1785         * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
1786         * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
1787         */
1788        public static final int EVENT_INPUT_HIDDEN = 2;
1789
1790        /**
1791         * The autofill input UI affordance associated with the view isn't shown because
1792         * autofill is not available.
1793         *
1794         * <p>If the view provides its own auto-complete UI affordance but was not displaying it
1795         * to avoid flickering, it could shown it upon receiving this event.
1796         */
1797        public static final int EVENT_INPUT_UNAVAILABLE = 3;
1798
1799        /**
1800         * Called after a change in the autofill state associated with a view.
1801         *
1802         * @param view view associated with the change.
1803         *
1804         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1805         */
1806        public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
1807        }
1808
1809        /**
1810         * Called after a change in the autofill state associated with a virtual view.
1811         *
1812         * @param view parent view associated with the change.
1813         * @param virtualId id identifying the virtual child inside the parent view.
1814         *
1815         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1816         */
1817        public void onAutofillEvent(@NonNull View view, int virtualId,
1818                @AutofillEventType int event) {
1819        }
1820    }
1821
1822    private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
1823        private final WeakReference<AutofillManager> mAfm;
1824
1825        AutofillManagerClient(AutofillManager autofillManager) {
1826            mAfm = new WeakReference<>(autofillManager);
1827        }
1828
1829        @Override
1830        public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
1831            final AutofillManager afm = mAfm.get();
1832            if (afm != null) {
1833                afm.post(() -> afm.setState(enabled, resetSession, resetClient));
1834            }
1835        }
1836
1837        @Override
1838        public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
1839            final AutofillManager afm = mAfm.get();
1840            if (afm != null) {
1841                afm.post(() -> afm.autofill(sessionId, ids, values));
1842            }
1843        }
1844
1845        @Override
1846        public void authenticate(int sessionId, int authenticationId, IntentSender intent,
1847                Intent fillInIntent) {
1848            final AutofillManager afm = mAfm.get();
1849            if (afm != null) {
1850                afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
1851            }
1852        }
1853
1854        @Override
1855        public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1856                Rect anchorBounds, IAutofillWindowPresenter presenter) {
1857            final AutofillManager afm = mAfm.get();
1858            if (afm != null) {
1859                afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
1860                        presenter));
1861            }
1862        }
1863
1864        @Override
1865        public void requestHideFillUi(int sessionId, AutofillId id) {
1866            final AutofillManager afm = mAfm.get();
1867            if (afm != null) {
1868                afm.post(() -> afm.requestHideFillUi(id));
1869            }
1870        }
1871
1872        @Override
1873        public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
1874            final AutofillManager afm = mAfm.get();
1875            if (afm != null) {
1876                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
1877            }
1878        }
1879
1880        @Override
1881        public void startIntentSender(IntentSender intentSender, Intent intent) {
1882            final AutofillManager afm = mAfm.get();
1883            if (afm != null) {
1884                afm.post(() -> {
1885                    try {
1886                        afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0);
1887                    } catch (IntentSender.SendIntentException e) {
1888                        Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
1889                    }
1890                });
1891            }
1892        }
1893
1894        @Override
1895        public void setTrackedViews(int sessionId, AutofillId[] ids,
1896                boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
1897            final AutofillManager afm = mAfm.get();
1898            if (afm != null) {
1899                afm.post(() ->
1900                        afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
1901                );
1902            }
1903        }
1904
1905        @Override
1906        public void setSaveUiState(int sessionId, boolean shown) {
1907            final AutofillManager afm = mAfm.get();
1908            if (afm != null) {
1909                afm.post(() -> afm.setSaveUiState(sessionId, shown));
1910            }
1911        }
1912
1913        @Override
1914        public void setSessionFinished(int newState) {
1915            final AutofillManager afm = mAfm.get();
1916            if (afm != null) {
1917                afm.post(() -> afm.setSessionFinished(newState));
1918            }
1919        }
1920    }
1921}
1922