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