blob: c123a807800f75dc1a46cdaee0a0098a31b83584 [file] [log] [blame]
Felipe Leme3461d3c2017-01-19 08:54:55 -08001/*
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
Felipe Leme73fedac2017-05-12 09:52:07 -070019import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
Felipe Leme9f9ee252017-04-27 13:56:22 -070020import static android.view.autofill.Helper.sDebug;
21import static android.view.autofill.Helper.sVerbose;
Felipe Lemed633f072017-02-14 10:17:17 -080022
Felipe Lemee6010f22017-03-03 11:19:51 -080023import android.annotation.IntDef;
Philip P. Moltmann44611812017-02-23 12:52:46 -080024import android.annotation.NonNull;
Felipe Lemee6010f22017-03-03 11:19:51 -080025import android.annotation.Nullable;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060026import android.annotation.SystemService;
Felipe Leme3461d3c2017-01-19 08:54:55 -080027import android.content.Context;
Svet Ganov782043c2017-02-11 00:52:02 +000028import android.content.Intent;
29import android.content.IntentSender;
Felipe Leme3461d3c2017-01-19 08:54:55 -080030import android.graphics.Rect;
Felipe Leme4753bb02017-03-22 20:24:00 -070031import android.metrics.LogMaker;
Svet Ganov782043c2017-02-11 00:52:02 +000032import android.os.Bundle;
Svet Ganov782043c2017-02-11 00:52:02 +000033import android.os.Parcelable;
Felipe Leme3461d3c2017-01-19 08:54:55 -080034import android.os.RemoteException;
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -070035import android.service.autofill.AutofillService;
36import android.service.autofill.FillEventHistory;
Felipe Leme4753bb02017-03-22 20:24:00 -070037import android.util.ArrayMap;
Philip P. Moltmann494c3f52017-04-11 10:13:33 -070038import android.util.ArraySet;
Felipe Leme3461d3c2017-01-19 08:54:55 -080039import android.util.Log;
Felipe Leme4753bb02017-03-22 20:24:00 -070040import android.util.SparseArray;
Felipe Leme3461d3c2017-01-19 08:54:55 -080041import android.view.View;
42
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070043import com.android.internal.annotations.GuardedBy;
Felipe Leme4753bb02017-03-22 20:24:00 -070044import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.nano.MetricsProto;
46
Felipe Lemee6010f22017-03-03 11:19:51 -080047import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
Svet Ganov782043c2017-02-11 00:52:02 +000049import java.lang.ref.WeakReference;
Philip P. Moltmann134cee22017-05-06 11:28:38 -070050import java.util.ArrayList;
Svet Ganov782043c2017-02-11 00:52:02 +000051import java.util.List;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -070052import java.util.Objects;
Svet Ganov782043c2017-02-11 00:52:02 +000053
Felipe Leme3461d3c2017-01-19 08:54:55 -080054/**
Felipe Leme744976e2017-07-31 11:34:14 -070055 * The {@link AutofillManager} provides ways for apps and custom views to integrate with the
56 * Autofill Framework lifecycle.
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070057 *
Felipe Leme744976e2017-07-31 11:34:14 -070058 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an
59 * activity context; the autofill context is created when one of the following methods is called for
60 * the first time in an activity context, and the current user has an enabled autofill service:
61 *
62 * <ul>
63 * <li>{@link #notifyViewEntered(View)}
64 * <li>{@link #notifyViewEntered(View, int, Rect)}
65 * <li>{@link #requestAutofill(View)}
66 * </ul>
67 *
68 * <p>Tipically, the context is automatically created when the first view of the activity is
69 * focused because {@code View.onFocusChanged()} indirectly calls
70 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to
71 * explicitly create it (for example, a custom view developer could offer a contextual menu action
72 * in a text-field view to let users manually request autofill).
73 *
74 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure}
75 * that represents the view hierarchy by calling
76 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views
77 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in
78 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and
79 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in
80 * the hierarchy.
81 *
82 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which
83 * parses it looking for views that can be autofilled. If the service finds such views, it returns
84 * a data structure to the Android System containing the following optional info:
85 *
86 * <ul>
87 * <li>Datasets used to autofill subsets of views in the activity.
88 * <li>Id of views that the service can save their values for future autofilling.
89 * </ul>
90 *
91 * <p>When the service returns datasets, the Android System displays an autofill dataset picker
92 * UI affordance associated with the view, when the view is focused on and is part of a dataset.
93 * The application can be notified when the affordance is shown by registering an
94 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
95 * selects a dataset from the affordance, all views present in the dataset are autofilled, through
96 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
97 *
98 * <p>When the service returns ids of savable views, the Android System keeps track of changes
99 * made to these views, so they can be used to determine if the autofill save UI is shown later.
100 *
101 * <p>The context is then finished when one of the following occurs:
102 *
103 * <ul>
104 * <li>{@link #commit()} is called or all savable views are gone.
105 * <li>{@link #cancel()} is called.
106 * </ul>
107 *
108 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
109 * shows a save UI affordance if the value of savable views have changed. If the user selects the
110 * option to Save, the current value of the views is then sent to the autofill service.
111 *
112 * <p>It is safe to call into its methods from any thread.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800113 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600114@SystemService(Context.AUTOFILL_MANAGER_SERVICE)
Felipe Leme640f30a2017-03-06 15:44:06 -0800115public final class AutofillManager {
Felipe Leme3461d3c2017-01-19 08:54:55 -0800116
Felipe Leme640f30a2017-03-06 15:44:06 -0800117 private static final String TAG = "AutofillManager";
Felipe Leme3461d3c2017-01-19 08:54:55 -0800118
Svet Ganov782043c2017-02-11 00:52:02 +0000119 /**
120 * Intent extra: The assist structure which captures the filled screen.
Felipe Leme7320ca92017-03-29 15:09:54 -0700121 *
Svet Ganov782043c2017-02-11 00:52:02 +0000122 * <p>
123 * Type: {@link android.app.assist.AssistStructure}
Svet Ganov782043c2017-02-11 00:52:02 +0000124 */
125 public static final String EXTRA_ASSIST_STRUCTURE =
126 "android.view.autofill.extra.ASSIST_STRUCTURE";
127
128 /**
129 * Intent extra: The result of an authentication operation. It is
130 * either a fully populated {@link android.service.autofill.FillResponse}
131 * or a fully populated {@link android.service.autofill.Dataset} if
132 * a response or a dataset is being authenticated respectively.
133 *
134 * <p>
135 * Type: {@link android.service.autofill.FillResponse} or a
136 * {@link android.service.autofill.Dataset}
Svet Ganov782043c2017-02-11 00:52:02 +0000137 */
138 public static final String EXTRA_AUTHENTICATION_RESULT =
139 "android.view.autofill.extra.AUTHENTICATION_RESULT";
140
Felipe Leme7320ca92017-03-29 15:09:54 -0700141 /**
142 * Intent extra: The optional extras provided by the
143 * {@link android.service.autofill.AutofillService}.
144 *
145 * <p>For example, when the service responds to a {@link
146 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
147 * a {@code FillResponse} that requires authentication, the Intent that launches the
148 * service authentication will contain the Bundle set by
Felipe Leme49e96962017-04-20 15:46:18 -0700149 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
Felipe Leme7320ca92017-03-29 15:09:54 -0700150 *
151 * <p>
152 * Type: {@link android.os.Bundle}
153 */
Felipe Leme37a440fd2017-04-28 10:50:20 -0700154 public static final String EXTRA_CLIENT_STATE =
Jeff Sharkey000ce802017-04-29 13:13:27 -0600155 "android.view.autofill.extra.CLIENT_STATE";
Felipe Leme7320ca92017-03-29 15:09:54 -0700156
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700157 static final String SESSION_ID_TAG = "android:sessionId";
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700158 static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
159
Felipe Leme0aa4c502017-04-26 12:36:01 -0700160 /** @hide */ public static final int ACTION_START_SESSION = 1;
161 /** @hide */ public static final int ACTION_VIEW_ENTERED = 2;
162 /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
163 /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800164
Felipe Leme9f9ee252017-04-27 13:56:22 -0700165
166 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
167 /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
168 /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
169
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700170 /** Which bits in an authentication id are used for the dataset id */
171 private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
172 /** How many bits in an authentication id are used for the dataset id */
173 private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
174 /** @hide The index for an undefined data set */
175 public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
176
177 /**
178 * Makes an authentication id from a request id and a dataset id.
179 *
180 * @param requestId The request id.
181 * @param datasetId The dataset id.
182 * @return The authentication id.
183 * @hide
184 */
185 public static int makeAuthenticationId(int requestId, int datasetId) {
186 return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT)
187 | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK);
188 }
189
190 /**
191 * Gets the request id from an authentication id.
192 *
193 * @param authRequestId The authentication id.
194 * @return The request id.
195 * @hide
196 */
197 public static int getRequestIdFromAuthenticationId(int authRequestId) {
198 return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT);
199 }
200
201 /**
202 * Gets the dataset id from an authentication id.
203 *
204 * @param authRequestId The authentication id.
205 * @return The dataset id.
206 * @hide
207 */
208 public static int getDatasetIdFromAuthenticationId(int authRequestId) {
209 return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK);
210 }
211
Felipe Leme4753bb02017-03-22 20:24:00 -0700212 private final MetricsLogger mMetricsLogger = new MetricsLogger();
Svet Ganov782043c2017-02-11 00:52:02 +0000213
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700214 /**
215 * There is currently no session running.
216 * {@hide}
217 */
218 public static final int NO_SESSION = Integer.MIN_VALUE;
219
Svet Ganov782043c2017-02-11 00:52:02 +0000220 private final IAutoFillManager mService;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700221
222 private final Object mLock = new Object();
223
224 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000225 private IAutoFillManagerClient mServiceClient;
226
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700227 @GuardedBy("mLock")
Felipe Lemee6010f22017-03-03 11:19:51 -0800228 private AutofillCallback mCallback;
229
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700230 private final Context mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000231
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700232 @GuardedBy("mLock")
233 private int mSessionId = NO_SESSION;
234
235 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000236 private boolean mEnabled;
237
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700238 /** If a view changes to this mapping the autofill operation was successful */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700239 @GuardedBy("mLock")
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700240 @Nullable private ParcelableMap mLastAutofilledData;
241
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700242 /** If view tracking is enabled, contains the tracking state */
243 @GuardedBy("mLock")
244 @Nullable private TrackedViews mTrackedViews;
245
Felipe Leme27e20222017-05-18 15:24:11 -0700246 /** Views that are only tracked because they are fillable and could be anchoring the UI. */
247 @GuardedBy("mLock")
248 @Nullable private ArraySet<AutofillId> mFillableIds;
249
Svet Ganov782043c2017-02-11 00:52:02 +0000250 /** @hide */
Felipe Leme640f30a2017-03-06 15:44:06 -0800251 public interface AutofillClient {
Svet Ganov782043c2017-02-11 00:52:02 +0000252 /**
Svet Ganov782043c2017-02-11 00:52:02 +0000253 * Asks the client to start an authentication flow.
254 *
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700255 * @param authenticationId A unique id of the authentication operation.
Svet Ganov782043c2017-02-11 00:52:02 +0000256 * @param intent The authentication intent.
257 * @param fillInIntent The authentication fill-in intent.
258 */
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700259 void autofillCallbackAuthenticate(int authenticationId, IntentSender intent,
260 Intent fillInIntent);
Svet Ganov17db9dc2017-02-21 19:54:31 -0800261
262 /**
263 * Tells the client this manager has state to be reset.
264 */
Felipe Leme4753bb02017-03-22 20:24:00 -0700265 void autofillCallbackResetableStateAvailable();
266
267 /**
268 * Request showing the autofill UI.
269 *
270 * @param anchor The real view the UI needs to anchor to.
271 * @param width The width of the fill UI content.
272 * @param height The height of the fill UI content.
273 * @param virtualBounds The bounds of the virtual decendant of the anchor.
274 * @param presenter The presenter that controls the fill UI window.
275 * @return Whether the UI was shown.
276 */
277 boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
278 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
279
280 /**
281 * Request hiding the autofill UI.
282 *
283 * @return Whether the UI was hidden.
284 */
285 boolean autofillCallbackRequestHideFillUi();
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700286
287 /**
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700288 * Checks if views are currently attached and visible.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700289 *
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700290 * @return And array with {@code true} iff the view is attached or visible
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700291 */
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700292 @NonNull boolean[] getViewVisibility(@NonNull int[] viewId);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700293
294 /**
295 * Checks is the client is currently visible as understood by autofill.
296 *
297 * @return {@code true} if the client is currently visible
298 */
299 boolean isVisibleForAutofill();
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700300
301 /**
Felipe Leme27e20222017-05-18 15:24:11 -0700302 * Finds views by traversing the hierarchies of the client.
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700303 *
Phil Weaver846cda932017-06-15 10:10:06 -0700304 * @param viewIds The autofill ids of the views to find
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700305 *
Felipe Leme27e20222017-05-18 15:24:11 -0700306 * @return And array containing the views (empty if no views found).
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700307 */
Phil Weaver846cda932017-06-15 10:10:06 -0700308 @NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds);
Felipe Leme27e20222017-05-18 15:24:11 -0700309
310 /**
311 * Finds a view by traversing the hierarchies of the client.
312 *
Phil Weaver846cda932017-06-15 10:10:06 -0700313 * @param viewId The autofill id of the views to find
Felipe Leme27e20222017-05-18 15:24:11 -0700314 *
315 * @return The view, or {@code null} if not found
316 */
Phil Weaver846cda932017-06-15 10:10:06 -0700317 @Nullable View findViewByAutofillIdTraversal(int viewId);
Felipe Leme9876a6f2017-05-30 15:47:28 -0700318
319 /**
320 * Runs the specified action on the UI thread.
321 */
322 void runOnUiThread(Runnable action);
Svet Ganov782043c2017-02-11 00:52:02 +0000323 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800324
325 /**
326 * @hide
327 */
Felipe Leme640f30a2017-03-06 15:44:06 -0800328 public AutofillManager(Context context, IAutoFillManager service) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800329 mContext = context;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800330 mService = service;
331 }
332
333 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700334 * Restore state after activity lifecycle
335 *
336 * @param savedInstanceState The state to be restored
337 *
338 * {@hide}
339 */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700340 public void onCreate(Bundle savedInstanceState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700341 if (!hasAutofillFeature()) {
342 return;
343 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700344 synchronized (mLock) {
345 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
346
347 if (mSessionId != NO_SESSION) {
348 Log.w(TAG, "New session was started before onCreate()");
349 return;
350 }
351
352 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
353
354 if (mSessionId != NO_SESSION) {
355 ensureServiceClientAddedIfNeededLocked();
356
357 final AutofillClient client = getClientLocked();
358 if (client != null) {
359 try {
360 final boolean sessionWasRestored = mService.restoreSession(mSessionId,
361 mContext.getActivityToken(), mServiceClient.asBinder());
362
363 if (!sessionWasRestored) {
364 Log.w(TAG, "Session " + mSessionId + " could not be restored");
365 mSessionId = NO_SESSION;
366 } else {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700367 if (sDebug) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700368 Log.d(TAG, "session " + mSessionId + " was restored");
369 }
370
371 client.autofillCallbackResetableStateAvailable();
372 }
373 } catch (RemoteException e) {
374 Log.e(TAG, "Could not figure out if there was an autofill session", e);
375 }
376 }
377 }
378 }
379 }
380
381 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700382 * Called once the client becomes visible.
383 *
384 * @see AutofillClient#isVisibleForAutofill()
385 *
386 * {@hide}
387 */
388 public void onVisibleForAutofill() {
389 synchronized (mLock) {
390 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700391 mTrackedViews.onVisibleForAutofillLocked();
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700392 }
393 }
394 }
395
396 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700397 * Save state before activity lifecycle
398 *
399 * @param outState Place to store the state
400 *
401 * {@hide}
402 */
403 public void onSaveInstanceState(Bundle outState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700404 if (!hasAutofillFeature()) {
405 return;
406 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700407 synchronized (mLock) {
408 if (mSessionId != NO_SESSION) {
409 outState.putInt(SESSION_ID_TAG, mSessionId);
410 }
411
412 if (mLastAutofilledData != null) {
413 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
414 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700415 }
416 }
417
418 /**
419 * Checks whether autofill is enabled for the current user.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700420 *
421 * <p>Typically used to determine whether the option to explicitly request autofill should
422 * be offered - see {@link #requestAutofill(View)}.
423 *
424 * @return whether autofill is enabled for the current user.
425 */
426 public boolean isEnabled() {
Svet Ganov43574b02017-04-12 09:25:20 -0700427 if (!hasAutofillFeature()) {
428 return false;
429 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700430 synchronized (mLock) {
431 ensureServiceClientAddedIfNeededLocked();
432 return mEnabled;
433 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700434 }
435
436 /**
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700437 * Should always be called from {@link AutofillService#getFillEventHistory()}.
438 *
439 * @hide
440 */
441 @Nullable public FillEventHistory getFillEventHistory() {
442 try {
443 return mService.getFillEventHistory();
444 } catch (RemoteException e) {
445 e.rethrowFromSystemServer();
446 return null;
447 }
448 }
449
450 /**
Felipe Leme2ac463e2017-03-13 14:06:25 -0700451 * Explicitly requests a new autofill context.
452 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700453 * <p>Normally, the autofill context is automatically started if necessary when
454 * {@link #notifyViewEntered(View)} is called, but this method should be used in the
455 * cases where it must be explicitly started. For example, when the view offers an AUTOFILL
456 * option on its contextual overflow menu, and the user selects it.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700457 *
458 * @param view view requesting the new autofill context.
459 */
460 public void requestAutofill(@NonNull View view) {
Felipe Lemed1146422017-04-26 10:17:05 -0700461 notifyViewEntered(view, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700462 }
463
464 /**
465 * Explicitly requests a new autofill context for virtual views.
466 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700467 * <p>Normally, the autofill context is automatically started if necessary when
468 * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the
469 * cases where it must be explicitly started. For example, when the virtual view offers an
470 * AUTOFILL option on its contextual overflow menu, and the user selects it.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700471 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700472 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
473 * parent view uses {@code bounds} to draw the virtual view inside its Canvas,
474 * the absolute bounds could be calculated by:
475 *
476 * <pre class="prettyprint">
477 * int offset[] = new int[2];
478 * getLocationOnScreen(offset);
479 * Rect absBounds = new Rect(bounds.left + offset[0],
480 * bounds.top + offset[1],
481 * bounds.right + offset[0], bounds.bottom + offset[1]);
482 * </pre>
483 *
484 * @param view the virtual view parent.
485 * @param virtualId id identifying the virtual child inside the parent view.
486 * @param absBounds absolute boundaries of the virtual view in the screen.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700487 */
Felipe Leme6dcec872017-05-25 11:24:23 -0700488 public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
489 notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700490 }
491
Felipe Leme2ac463e2017-03-13 14:06:25 -0700492 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700493 * Called when a {@link View} that supports autofill is entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800494 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700495 * @param view {@link View} that was entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800496 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700497 public void notifyViewEntered(@NonNull View view) {
Felipe Lemed1146422017-04-26 10:17:05 -0700498 notifyViewEntered(view, 0);
499 }
500
501 private void notifyViewEntered(@NonNull View view, int flags) {
Svet Ganov43574b02017-04-12 09:25:20 -0700502 if (!hasAutofillFeature()) {
503 return;
504 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700505 AutofillCallback callback = null;
506 synchronized (mLock) {
507 ensureServiceClientAddedIfNeededLocked();
Svet Ganov782043c2017-02-11 00:52:02 +0000508
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700509 if (!mEnabled) {
510 if (mCallback != null) {
511 callback = mCallback;
512 }
513 } else {
514 final AutofillId id = getAutofillId(view);
515 final AutofillValue value = view.getAutofillValue();
516
517 if (mSessionId == NO_SESSION) {
518 // Starts new session.
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700519 startSessionLocked(id, null, value, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700520 } else {
521 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -0700522 updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700523 }
Felipe Leme24aae152017-03-15 12:33:01 -0700524 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800525 }
526
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700527 if (callback != null) {
528 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Svet Ganov782043c2017-02-11 00:52:02 +0000529 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800530 }
531
532 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700533 * Called when a {@link View} that supports autofill is exited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800534 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700535 * @param view {@link View} that was exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800536 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700537 public void notifyViewExited(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700538 if (!hasAutofillFeature()) {
539 return;
540 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700541 synchronized (mLock) {
542 ensureServiceClientAddedIfNeededLocked();
Philip P. Moltmann44611812017-02-23 12:52:46 -0800543
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700544 if (mEnabled && mSessionId != NO_SESSION) {
545 final AutofillId id = getAutofillId(view);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800546
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700547 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -0700548 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700549 }
Philip P. Moltmann44611812017-02-23 12:52:46 -0800550 }
551 }
552
553 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700554 * Called when a {@link View view's} visibility changes.
555 *
556 * @param view {@link View} that was exited.
557 * @param isVisible visible if the view is visible in the view hierarchy.
558 *
559 * @hide
560 */
561 public void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
562 synchronized (mLock) {
Felipe Leme27e20222017-05-18 15:24:11 -0700563 if (mEnabled && mSessionId != NO_SESSION) {
564 if (!isVisible && mFillableIds != null) {
565 final AutofillId id = view.getAutofillId();
566 if (mFillableIds.contains(id)) {
567 if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible");
568 requestHideFillUi(id, view);
569 }
570 }
571 if (mTrackedViews != null) {
572 mTrackedViews.notifyViewVisibilityChange(view, isVisible);
573 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700574 }
575 }
576 }
577
578 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700579 * Called when a virtual view that supports autofill is entered.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800580 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700581 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
582 * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas,
583 * the absolute bounds could be calculated by:
584 *
585 * <pre class="prettyprint">
586 * int offset[] = new int[2];
587 * getLocationOnScreen(offset);
588 * Rect absBounds = new Rect(bounds.left + offset[0],
589 * bounds.top + offset[1],
590 * bounds.right + offset[0], bounds.bottom + offset[1]);
591 * </pre>
592 *
593 * @param view the virtual view parent.
594 * @param virtualId id identifying the virtual child inside the parent view.
595 * @param absBounds absolute boundaries of the virtual view in the screen.
Felipe Lemebab851c2017-02-03 18:45:08 -0800596 */
Felipe Leme6dcec872017-05-25 11:24:23 -0700597 public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
598 notifyViewEntered(view, virtualId, absBounds, 0);
Felipe Lemed1146422017-04-26 10:17:05 -0700599 }
600
Felipe Leme6dcec872017-05-25 11:24:23 -0700601 private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) {
Svet Ganov43574b02017-04-12 09:25:20 -0700602 if (!hasAutofillFeature()) {
603 return;
604 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700605 AutofillCallback callback = null;
606 synchronized (mLock) {
607 ensureServiceClientAddedIfNeededLocked();
Svet Ganov782043c2017-02-11 00:52:02 +0000608
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700609 if (!mEnabled) {
610 if (mCallback != null) {
611 callback = mCallback;
612 }
613 } else {
Felipe Leme6dcec872017-05-25 11:24:23 -0700614 final AutofillId id = getAutofillId(view, virtualId);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700615
616 if (mSessionId == NO_SESSION) {
617 // Starts new session.
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700618 startSessionLocked(id, bounds, null, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700619 } else {
620 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -0700621 updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700622 }
Felipe Leme24aae152017-03-15 12:33:01 -0700623 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800624 }
625
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700626 if (callback != null) {
Felipe Leme6dcec872017-05-25 11:24:23 -0700627 callback.onAutofillEvent(view, virtualId,
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700628 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800629 }
630 }
631
632 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700633 * Called when a virtual view that supports autofill is exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800634 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700635 * @param view the virtual view parent.
636 * @param virtualId id identifying the virtual child inside the parent view.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800637 */
Felipe Leme6dcec872017-05-25 11:24:23 -0700638 public void notifyViewExited(@NonNull View view, int virtualId) {
Svet Ganov43574b02017-04-12 09:25:20 -0700639 if (!hasAutofillFeature()) {
640 return;
641 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700642 synchronized (mLock) {
643 ensureServiceClientAddedIfNeededLocked();
Philip P. Moltmann44611812017-02-23 12:52:46 -0800644
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700645 if (mEnabled && mSessionId != NO_SESSION) {
Felipe Leme6dcec872017-05-25 11:24:23 -0700646 final AutofillId id = getAutofillId(view, virtualId);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800647
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700648 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -0700649 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700650 }
Svet Ganov782043c2017-02-11 00:52:02 +0000651 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800652 }
653
654 /**
Felipe Leme640f30a2017-03-06 15:44:06 -0800655 * Called to indicate the value of an autofillable {@link View} changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800656 *
Philip P. Moltmann44611812017-02-23 12:52:46 -0800657 * @param view view whose value changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800658 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700659 public void notifyValueChanged(View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700660 if (!hasAutofillFeature()) {
661 return;
662 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700663 AutofillId id = null;
664 boolean valueWasRead = false;
665 AutofillValue value = null;
666
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700667 synchronized (mLock) {
668 // If the session is gone some fields might still be highlighted, hence we have to
669 // remove the isAutofilled property even if no sessions are active.
670 if (mLastAutofilledData == null) {
671 view.setAutofilled(false);
672 } else {
673 id = getAutofillId(view);
674 if (mLastAutofilledData.containsKey(id)) {
675 value = view.getAutofillValue();
676 valueWasRead = true;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700677
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700678 if (Objects.equals(mLastAutofilledData.get(id), value)) {
679 view.setAutofilled(true);
680 } else {
681 view.setAutofilled(false);
682 mLastAutofilledData.remove(id);
683 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700684 } else {
685 view.setAutofilled(false);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700686 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700687 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700688
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700689 if (!mEnabled || mSessionId == NO_SESSION) {
690 return;
691 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800692
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700693 if (id == null) {
694 id = getAutofillId(view);
695 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700696
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700697 if (!valueWasRead) {
698 value = view.getAutofillValue();
699 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700700
Felipe Leme0aa4c502017-04-26 12:36:01 -0700701 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700702 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800703 }
704
Felipe Lemebab851c2017-02-03 18:45:08 -0800705 /**
Felipe Leme6dcec872017-05-25 11:24:23 -0700706 * Called to indicate the value of an autofillable virtual view has changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800707 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700708 * @param view the virtual view parent.
709 * @param virtualId id identifying the virtual child inside the parent view.
Felipe Lemebab851c2017-02-03 18:45:08 -0800710 * @param value new value of the child.
711 */
Felipe Leme6dcec872017-05-25 11:24:23 -0700712 public void notifyValueChanged(View view, int virtualId, AutofillValue value) {
Svet Ganov43574b02017-04-12 09:25:20 -0700713 if (!hasAutofillFeature()) {
714 return;
715 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700716 synchronized (mLock) {
717 if (!mEnabled || mSessionId == NO_SESSION) {
718 return;
719 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800720
Felipe Leme6dcec872017-05-25 11:24:23 -0700721 final AutofillId id = getAutofillId(view, virtualId);
Felipe Leme0aa4c502017-04-26 12:36:01 -0700722 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700723 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800724 }
725
726 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700727 * Called to indicate the current autofill context should be commited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800728 *
Felipe Leme33681a92017-07-28 09:14:38 -0700729 * <p>This method is typically called by {@link View Views} that manage virtual views; for
730 * example, when the view is rendering an {@code HTML} page with a form and virtual views
731 * that represent the HTML elements, it should call this method after the form is submitted and
732 * another page is rendered.
733 *
734 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
735 * methods such as {@link android.app.Activity#finish()}.
Felipe Lemebab851c2017-02-03 18:45:08 -0800736 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700737 public void commit() {
Svet Ganov43574b02017-04-12 09:25:20 -0700738 if (!hasAutofillFeature()) {
739 return;
740 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700741 synchronized (mLock) {
742 if (!mEnabled && mSessionId == NO_SESSION) {
743 return;
744 }
Svet Ganov782043c2017-02-11 00:52:02 +0000745
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700746 finishSessionLocked();
747 }
Felipe Leme0200d9e2017-01-24 15:10:26 -0800748 }
749
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700750 /**
751 * Called to indicate the current autofill context should be cancelled.
752 *
Felipe Leme33681a92017-07-28 09:14:38 -0700753 * <p>This method is typically called by {@link View Views} that manage virtual views; for
754 * example, when the view is rendering an {@code HTML} page with a form and virtual views
755 * that represent the HTML elements, it should call this method if the user does not post the
756 * form but moves to another form in this page.
757 *
758 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
759 * methods such as {@link android.app.Activity#finish()}.
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700760 */
761 public void cancel() {
Svet Ganov43574b02017-04-12 09:25:20 -0700762 if (!hasAutofillFeature()) {
763 return;
764 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700765 synchronized (mLock) {
766 if (!mEnabled && mSessionId == NO_SESSION) {
767 return;
768 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700769
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700770 cancelSessionLocked();
771 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700772 }
773
Svet Ganovf965b872017-04-28 16:34:02 -0700774 /** @hide */
775 public void disableOwnedAutofillServices() {
776 disableAutofillServices();
777 }
778
Svet Ganovf20a0372017-04-10 17:08:05 -0700779 /**
780 * If the app calling this API has enabled autofill services they
781 * will be disabled.
782 */
Svet Ganovf965b872017-04-28 16:34:02 -0700783 public void disableAutofillServices() {
Svet Ganov43574b02017-04-12 09:25:20 -0700784 if (!hasAutofillFeature()) {
785 return;
786 }
Svet Ganovf20a0372017-04-10 17:08:05 -0700787 try {
788 mService.disableOwnedAutofillServices(mContext.getUserId());
789 } catch (RemoteException e) {
790 throw e.rethrowFromSystemServer();
791 }
792 }
793
Felipe Lemedb041182017-04-21 17:33:38 -0700794 /**
795 * Returns {@code true} if the calling application provides a {@link AutofillService} that is
796 * enabled for the current user, or {@code false} otherwise.
797 */
798 public boolean hasEnabledAutofillServices() {
799 if (mService == null) return false;
800
801 try {
802 return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
803 } catch (RemoteException e) {
804 throw e.rethrowFromSystemServer();
805 }
806 }
807
808 /**
Ricardo Loo33d226c2017-06-27 14:17:33 -0700809 * Returns {@code true} if autofill is supported by the current device and
810 * is supported for this user.
Felipe Lemedb041182017-04-21 17:33:38 -0700811 *
812 * <p>Autofill is typically supported, but it could be unsupported in cases like:
813 * <ol>
814 * <li>Low-end devices.
815 * <li>Device policy rules that forbid its usage.
816 * </ol>
817 */
818 public boolean isAutofillSupported() {
819 if (mService == null) return false;
820
821 try {
822 return mService.isServiceSupported(mContext.getUserId());
823 } catch (RemoteException e) {
824 throw e.rethrowFromSystemServer();
825 }
826 }
827
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700828 private AutofillClient getClientLocked() {
Felipe Leme640f30a2017-03-06 15:44:06 -0800829 if (mContext instanceof AutofillClient) {
830 return (AutofillClient) mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000831 }
Svet Ganov17db9dc2017-02-21 19:54:31 -0800832 return null;
Svet Ganov782043c2017-02-11 00:52:02 +0000833 }
834
835 /** @hide */
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700836 public void onAuthenticationResult(int authenticationId, Intent data) {
Svet Ganov43574b02017-04-12 09:25:20 -0700837 if (!hasAutofillFeature()) {
838 return;
839 }
Felipe Leme85d1c2d2017-04-21 08:56:04 -0700840 // TODO: the result code is being ignored, so this method is not reliably
Felipe Lemed633f072017-02-14 10:17:17 -0800841 // handling the cases where it's not RESULT_OK: it works fine if the service does not
842 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
843 // service set the extra and returned RESULT_CANCELED...
844
Felipe Leme9f9ee252017-04-27 13:56:22 -0700845 if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data);
Felipe Lemed633f072017-02-14 10:17:17 -0800846
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700847 synchronized (mLock) {
848 if (mSessionId == NO_SESSION || data == null) {
849 return;
850 }
851 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
852 final Bundle responseData = new Bundle();
853 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
854 try {
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700855 mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
856 mContext.getUserId());
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700857 } catch (RemoteException e) {
858 Log.e(TAG, "Error delivering authentication result", e);
859 }
Svet Ganov782043c2017-02-11 00:52:02 +0000860 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800861 }
862
Felipe Leme640f30a2017-03-06 15:44:06 -0800863 private static AutofillId getAutofillId(View view) {
Phil Weaver846cda932017-06-15 10:10:06 -0700864 return new AutofillId(view.getAutofillViewId());
Felipe Leme0200d9e2017-01-24 15:10:26 -0800865 }
866
Felipe Leme6dcec872017-05-25 11:24:23 -0700867 private static AutofillId getAutofillId(View parent, int virtualId) {
Phil Weaver846cda932017-06-15 10:10:06 -0700868 return new AutofillId(parent.getAutofillViewId(), virtualId);
Felipe Lemebab851c2017-02-03 18:45:08 -0800869 }
870
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700871 private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
872 @NonNull AutofillValue value, int flags) {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700873 if (sVerbose) {
874 Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
Felipe Leme2ac463e2017-03-13 14:06:25 -0700875 + ", flags=" + flags);
Felipe Lemebab851c2017-02-03 18:45:08 -0800876 }
Felipe Lemee6010f22017-03-03 11:19:51 -0800877
Felipe Lemebab851c2017-02-03 18:45:08 -0800878 try {
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700879 mSessionId = mService.startSession(mContext.getActivityToken(),
Felipe Lemee6010f22017-03-03 11:19:51 -0800880 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
Philip P. Moltmann7b771162017-03-03 17:22:57 -0800881 mCallback != null, flags, mContext.getOpPackageName());
Felipe Leme0aa4c502017-04-26 12:36:01 -0700882 final AutofillClient client = getClientLocked();
Svet Ganov17db9dc2017-02-21 19:54:31 -0800883 if (client != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700884 client.autofillCallbackResetableStateAvailable();
Svet Ganov17db9dc2017-02-21 19:54:31 -0800885 }
Svet Ganov782043c2017-02-11 00:52:02 +0000886 } catch (RemoteException e) {
887 throw e.rethrowFromSystemServer();
888 }
889 }
890
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700891 private void finishSessionLocked() {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700892 if (sVerbose) Log.v(TAG, "finishSessionLocked()");
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700893
Svet Ganov782043c2017-02-11 00:52:02 +0000894 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700895 mService.finishSession(mSessionId, mContext.getUserId());
Felipe Lemebab851c2017-02-03 18:45:08 -0800896 } catch (RemoteException e) {
897 throw e.rethrowFromSystemServer();
898 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700899
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700900 mTrackedViews = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700901 mSessionId = NO_SESSION;
Felipe Lemebab851c2017-02-03 18:45:08 -0800902 }
903
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700904 private void cancelSessionLocked() {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700905 if (sVerbose) Log.v(TAG, "cancelSessionLocked()");
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700906
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700907 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700908 mService.cancelSession(mSessionId, mContext.getUserId());
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700909 } catch (RemoteException e) {
910 throw e.rethrowFromSystemServer();
911 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700912
Svet Ganov48f10a22017-04-26 18:49:30 -0700913 resetSessionLocked();
914 }
915
916 private void resetSessionLocked() {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700917 mSessionId = NO_SESSION;
Svet Ganov48f10a22017-04-26 18:49:30 -0700918 mTrackedViews = null;
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700919 }
920
Felipe Leme0aa4c502017-04-26 12:36:01 -0700921 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
922 int flags) {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700923 if (sVerbose && action != ACTION_VIEW_EXITED) {
924 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
925 + ", value=" + value + ", action=" + action + ", flags=" + flags);
Felipe Lemebab851c2017-02-03 18:45:08 -0800926 }
Felipe Leme0ab53dc2017-02-23 08:33:18 -0800927
Felipe Leme7f33cd32017-05-11 10:10:49 -0700928 boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0;
929
Felipe Leme3461d3c2017-01-19 08:54:55 -0800930 try {
Felipe Leme7f33cd32017-05-11 10:10:49 -0700931 if (restartIfNecessary) {
932 final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
933 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
934 mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
935 if (newId != mSessionId) {
936 if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
937 mSessionId = newId;
938 final AutofillClient client = getClientLocked();
939 if (client != null) {
940 client.autofillCallbackResetableStateAvailable();
941 }
942 }
943 } else {
944 mService.updateSession(mSessionId, id, bounds, value, action, flags,
945 mContext.getUserId());
946 }
947
Felipe Leme3461d3c2017-01-19 08:54:55 -0800948 } catch (RemoteException e) {
949 throw e.rethrowFromSystemServer();
950 }
951 }
Svet Ganov782043c2017-02-11 00:52:02 +0000952
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700953 private void ensureServiceClientAddedIfNeededLocked() {
954 if (getClientLocked() == null) {
Svet Ganov782043c2017-02-11 00:52:02 +0000955 return;
956 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700957
Svet Ganov782043c2017-02-11 00:52:02 +0000958 if (mServiceClient == null) {
Felipe Leme640f30a2017-03-06 15:44:06 -0800959 mServiceClient = new AutofillManagerClient(this);
Svet Ganov782043c2017-02-11 00:52:02 +0000960 try {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700961 final int flags = mService.addClient(mServiceClient, mContext.getUserId());
962 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
963 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
964 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
Svet Ganov782043c2017-02-11 00:52:02 +0000965 } catch (RemoteException e) {
966 throw e.rethrowFromSystemServer();
967 }
968 }
969 }
970
Felipe Lemee6010f22017-03-03 11:19:51 -0800971 /**
972 * Registers a {@link AutofillCallback} to receive autofill events.
973 *
974 * @param callback callback to receive events.
975 */
976 public void registerCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -0700977 if (!hasAutofillFeature()) {
978 return;
979 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700980 synchronized (mLock) {
981 if (callback == null) return;
Felipe Lemee6010f22017-03-03 11:19:51 -0800982
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700983 final boolean hadCallback = mCallback != null;
984 mCallback = callback;
Felipe Lemee6010f22017-03-03 11:19:51 -0800985
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700986 if (!hadCallback) {
987 try {
988 mService.setHasCallback(mSessionId, mContext.getUserId(), true);
989 } catch (RemoteException e) {
990 throw e.rethrowFromSystemServer();
991 }
Felipe Lemee6010f22017-03-03 11:19:51 -0800992 }
993 }
994 }
995
996 /**
997 * Unregisters a {@link AutofillCallback} to receive autofill events.
998 *
999 * @param callback callback to stop receiving events.
1000 */
1001 public void unregisterCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -07001002 if (!hasAutofillFeature()) {
1003 return;
1004 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001005 synchronized (mLock) {
1006 if (callback == null || mCallback == null || callback != mCallback) return;
Felipe Lemee6010f22017-03-03 11:19:51 -08001007
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001008 mCallback = null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001009
Felipe Lemee6010f22017-03-03 11:19:51 -08001010 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001011 mService.setHasCallback(mSessionId, mContext.getUserId(), false);
Felipe Lemee6010f22017-03-03 11:19:51 -08001012 } catch (RemoteException e) {
1013 throw e.rethrowFromSystemServer();
1014 }
1015 }
1016 }
1017
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001018 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1019 Rect anchorBounds, IAutofillWindowPresenter presenter) {
1020 final View anchor = findView(id);
Felipe Leme4753bb02017-03-22 20:24:00 -07001021 if (anchor == null) {
1022 return;
1023 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001024
1025 AutofillCallback callback = null;
1026 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001027 if (mSessionId == sessionId) {
1028 AutofillClient client = getClientLocked();
1029
1030 if (client != null) {
1031 if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
1032 anchorBounds, presenter) && mCallback != null) {
1033 callback = mCallback;
1034 }
1035 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001036 }
1037 }
1038
1039 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001040 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001041 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001042 AutofillCallback.EVENT_INPUT_SHOWN);
1043 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001044 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
Felipe Leme4753bb02017-03-22 20:24:00 -07001045 }
1046 }
1047 }
1048
Svetoslav Ganova9379d02017-05-09 17:40:24 -07001049 private void authenticate(int sessionId, int authenticationId, IntentSender intent,
1050 Intent fillInIntent) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001051 synchronized (mLock) {
1052 if (sessionId == mSessionId) {
1053 AutofillClient client = getClientLocked();
1054 if (client != null) {
Svetoslav Ganova9379d02017-05-09 17:40:24 -07001055 client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001056 }
1057 }
1058 }
1059 }
1060
Svet Ganov48f10a22017-04-26 18:49:30 -07001061 private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001062 synchronized (mLock) {
1063 mEnabled = enabled;
Svet Ganov48f10a22017-04-26 18:49:30 -07001064 if (!mEnabled || resetSession) {
1065 // Reset the session state
1066 resetSessionLocked();
1067 }
1068 if (resetClient) {
1069 // Reset connection to system
1070 mServiceClient = null;
1071 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001072 }
1073 }
1074
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001075 /**
1076 * Sets a view as autofilled if the current value is the {code targetValue}.
1077 *
1078 * @param view The view that is to be autofilled
1079 * @param targetValue The value we want to fill into view
1080 */
1081 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
1082 AutofillValue currentValue = view.getAutofillValue();
1083 if (Objects.equals(currentValue, targetValue)) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001084 synchronized (mLock) {
1085 if (mLastAutofilledData == null) {
1086 mLastAutofilledData = new ParcelableMap(1);
1087 }
1088 mLastAutofilledData.put(getAutofillId(view), targetValue);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001089 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001090 view.setAutofilled(true);
1091 }
1092 }
1093
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001094 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001095 synchronized (mLock) {
1096 if (sessionId != mSessionId) {
1097 return;
Felipe Leme4753bb02017-03-22 20:24:00 -07001098 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001099
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001100 final AutofillClient client = getClientLocked();
1101 if (client == null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001102 return;
1103 }
1104
1105 final int itemCount = ids.size();
1106 int numApplied = 0;
1107 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
Phil Weaver846cda932017-06-15 10:10:06 -07001108 final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001109
1110 for (int i = 0; i < itemCount; i++) {
1111 final AutofillId id = ids.get(i);
1112 final AutofillValue value = values.get(i);
1113 final int viewId = id.getViewId();
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001114 final View view = views[i];
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001115 if (view == null) {
1116 Log.w(TAG, "autofill(): no View with id " + viewId);
1117 continue;
Felipe Leme4753bb02017-03-22 20:24:00 -07001118 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001119 if (id.isVirtual()) {
1120 if (virtualValues == null) {
1121 // Most likely there will be just one view with virtual children.
1122 virtualValues = new ArrayMap<>(1);
1123 }
1124 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
1125 if (valuesByParent == null) {
1126 // We don't know the size yet, but usually it will be just a few fields...
1127 valuesByParent = new SparseArray<>(5);
1128 virtualValues.put(view, valuesByParent);
1129 }
1130 valuesByParent.put(id.getVirtualChildId(), value);
1131 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001132 // Mark the view as to be autofilled with 'value'
1133 if (mLastAutofilledData == null) {
1134 mLastAutofilledData = new ParcelableMap(itemCount - i);
1135 }
1136 mLastAutofilledData.put(id, value);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001137
1138 view.autofill(value);
1139
1140 // Set as autofilled if the values match now, e.g. when the value was updated
1141 // synchronously.
1142 // If autofill happens async, the view is set to autofilled in
1143 // notifyValueChanged.
1144 setAutofilledIfValuesIs(view, value);
1145
1146 numApplied++;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001147 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001148 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001149
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001150 if (virtualValues != null) {
1151 for (int i = 0; i < virtualValues.size(); i++) {
1152 final View parent = virtualValues.keyAt(i);
1153 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
1154 parent.autofill(childrenValues);
1155 numApplied += childrenValues.size();
1156 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001157 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001158
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001159 final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
1160 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
1161 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
1162 numApplied);
1163 mMetricsLogger.write(log);
1164 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001165 }
1166
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001167 /**
1168 * Set the tracked views.
1169 *
1170 * @param trackedIds The views to be tracked
1171 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
Felipe Leme27e20222017-05-18 15:24:11 -07001172 * @param fillableIds Views that might anchor FillUI.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001173 */
Felipe Leme27e20222017-05-18 15:24:11 -07001174 private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
1175 boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001176 synchronized (mLock) {
1177 if (mEnabled && mSessionId == sessionId) {
1178 if (saveOnAllViewsInvisible) {
1179 mTrackedViews = new TrackedViews(trackedIds);
1180 } else {
1181 mTrackedViews = null;
1182 }
Felipe Leme27e20222017-05-18 15:24:11 -07001183 if (fillableIds != null) {
1184 if (mFillableIds == null) {
1185 mFillableIds = new ArraySet<>(fillableIds.length);
1186 }
1187 for (AutofillId id : fillableIds) {
1188 mFillableIds.add(id);
1189 }
1190 if (sVerbose) {
1191 Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
1192 + ", mFillableIds" + mFillableIds);
1193 }
1194 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001195 }
1196 }
1197 }
1198
Felipe Leme27e20222017-05-18 15:24:11 -07001199 private void requestHideFillUi(AutofillId id) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001200 final View anchor = findView(id);
Felipe Leme27e20222017-05-18 15:24:11 -07001201 if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001202 if (anchor == null) {
1203 return;
1204 }
Felipe Leme27e20222017-05-18 15:24:11 -07001205 requestHideFillUi(id, anchor);
1206 }
1207
1208 private void requestHideFillUi(AutofillId id, View anchor) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001209
1210 AutofillCallback callback = null;
1211 synchronized (mLock) {
Svet Ganov48f10a22017-04-26 18:49:30 -07001212 // We do not check the session id for two reasons:
1213 // 1. If local and remote session id are off sync the UI would be stuck shown
1214 // 2. There is a race between the user state being destroyed due the fill
1215 // service being uninstalled and the UI being dismissed.
1216 AutofillClient client = getClientLocked();
1217 if (client != null) {
1218 if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
1219 callback = mCallback;
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001220 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001221 }
1222 }
1223
1224 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001225 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001226 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001227 AutofillCallback.EVENT_INPUT_HIDDEN);
1228 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001229 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
Felipe Leme4753bb02017-03-22 20:24:00 -07001230 }
1231 }
1232 }
1233
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001234 private void notifyNoFillUi(int sessionId, AutofillId id) {
1235 final View anchor = findView(id);
1236 if (anchor == null) {
1237 return;
1238 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001239
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001240 AutofillCallback callback = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001241 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001242 if (mSessionId == sessionId && getClientLocked() != null) {
1243 callback = mCallback;
1244 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001245 }
1246
1247 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001248 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001249 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001250 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1251 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001252 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Felipe Leme4753bb02017-03-22 20:24:00 -07001253 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001254
Felipe Leme4753bb02017-03-22 20:24:00 -07001255 }
1256 }
1257
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001258 /**
1259 * Get an array of viewIds from a List of {@link AutofillId}.
1260 *
1261 * @param autofillIds The autofill ids to convert
1262 *
1263 * @return The array of viewIds.
1264 */
Felipe Leme27e20222017-05-18 15:24:11 -07001265 // TODO: move to Helper as static method
1266 @NonNull private int[] getViewIds(@NonNull AutofillId[] autofillIds) {
1267 final int numIds = autofillIds.length;
1268 final int[] viewIds = new int[numIds];
1269 for (int i = 0; i < numIds; i++) {
1270 viewIds[i] = autofillIds[i].getViewId();
1271 }
1272
1273 return viewIds;
1274 }
1275
1276 // TODO: move to Helper as static method
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001277 @NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) {
1278 final int numIds = autofillIds.size();
1279 final int[] viewIds = new int[numIds];
1280 for (int i = 0; i < numIds; i++) {
1281 viewIds[i] = autofillIds.get(i).getViewId();
1282 }
1283
1284 return viewIds;
1285 }
1286
1287 /**
1288 * Find a single view by its id.
1289 *
1290 * @param autofillId The autofill id of the view
1291 *
1292 * @return The view or {@code null} if view was not found
1293 */
1294 private View findView(@NonNull AutofillId autofillId) {
1295 final AutofillClient client = getClientLocked();
1296
1297 if (client == null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001298 return null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001299 }
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001300
Phil Weaver846cda932017-06-15 10:10:06 -07001301 return client.findViewByAutofillIdTraversal(autofillId.getViewId());
Felipe Lemee6010f22017-03-03 11:19:51 -08001302 }
1303
Felipe Lemebb810922017-04-25 15:54:06 -07001304 /** @hide */
1305 public boolean hasAutofillFeature() {
Svet Ganov43574b02017-04-12 09:25:20 -07001306 return mService != null;
1307 }
1308
Felipe Leme9876a6f2017-05-30 15:47:28 -07001309 private void post(Runnable runnable) {
1310 final AutofillClient client = getClientLocked();
1311 if (client == null) {
1312 if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
1313 return;
1314 }
1315 client.runOnUiThread(runnable);
1316 }
1317
Felipe Lemee6010f22017-03-03 11:19:51 -08001318 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001319 * View tracking information. Once all tracked views become invisible the session is finished.
1320 */
1321 private class TrackedViews {
1322 /** Visible tracked views */
1323 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
1324
1325 /** Invisible tracked views */
1326 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
1327
1328 /**
1329 * Check if set is null or value is in set.
1330 *
1331 * @param set The set or null (== empty set)
1332 * @param value The value that might be in the set
1333 *
1334 * @return {@code true} iff set is not empty and value is in set
1335 */
Felipe Leme27e20222017-05-18 15:24:11 -07001336 // TODO: move to Helper as static method
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001337 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
1338 return set != null && set.contains(value);
1339 }
1340
1341 /**
1342 * Add a value to a set. If set is null, create a new set.
1343 *
1344 * @param set The set or null (== empty set)
1345 * @param valueToAdd The value to add
1346 *
1347 * @return The set including the new value. If set was {@code null}, a set containing only
1348 * the new value.
1349 */
Felipe Leme27e20222017-05-18 15:24:11 -07001350 // TODO: move to Helper as static method
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001351 @NonNull
1352 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
1353 if (set == null) {
1354 set = new ArraySet<>(1);
1355 }
1356
1357 set.add(valueToAdd);
1358
1359 return set;
1360 }
1361
1362 /**
1363 * Remove a value from a set.
1364 *
1365 * @param set The set or null (== empty set)
1366 * @param valueToRemove The value to remove
1367 *
1368 * @return The set without the removed value. {@code null} if set was null, or is empty
1369 * after removal.
1370 */
Felipe Leme27e20222017-05-18 15:24:11 -07001371 // TODO: move to Helper as static method
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001372 @Nullable
1373 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
1374 if (set == null) {
1375 return null;
1376 }
1377
1378 set.remove(valueToRemove);
1379
1380 if (set.isEmpty()) {
1381 return null;
1382 }
1383
1384 return set;
1385 }
1386
1387 /**
1388 * Set the tracked views.
1389 *
1390 * @param trackedIds The views to be tracked
1391 */
Felipe Leme27e20222017-05-18 15:24:11 -07001392 TrackedViews(@Nullable AutofillId[] trackedIds) {
1393 final AutofillClient client = getClientLocked();
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001394 if (trackedIds != null && client != null) {
1395 final boolean[] isVisible;
1396
1397 if (client.isVisibleForAutofill()) {
1398 isVisible = client.getViewVisibility(getViewIds(trackedIds));
1399 } else {
1400 // All false
Felipe Leme27e20222017-05-18 15:24:11 -07001401 isVisible = new boolean[trackedIds.length];
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001402 }
1403
Felipe Leme27e20222017-05-18 15:24:11 -07001404 final int numIds = trackedIds.length;
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001405 for (int i = 0; i < numIds; i++) {
Felipe Leme27e20222017-05-18 15:24:11 -07001406 final AutofillId id = trackedIds[i];
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001407
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001408 if (isVisible[i]) {
Philip P. Moltmanne0e28712017-04-20 15:19:06 -07001409 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001410 } else {
1411 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1412 }
1413 }
1414 }
1415
Felipe Leme9f9ee252017-04-27 13:56:22 -07001416 if (sVerbose) {
1417 Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001418 + " mVisibleTrackedIds=" + mVisibleTrackedIds
1419 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
1420 }
1421
1422 if (mVisibleTrackedIds == null) {
1423 finishSessionLocked();
1424 }
1425 }
1426
1427 /**
1428 * Called when a {@link View view's} visibility changes.
1429 *
1430 * @param view {@link View} that was exited.
1431 * @param isVisible visible if the view is visible in the view hierarchy.
1432 */
1433 void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
1434 AutofillId id = getAutofillId(view);
1435 AutofillClient client = getClientLocked();
1436
Felipe Leme9f9ee252017-04-27 13:56:22 -07001437 if (sDebug) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001438 Log.d(TAG, "notifyViewVisibilityChange(): id=" + id + " isVisible="
1439 + isVisible);
1440 }
1441
1442 if (client != null && client.isVisibleForAutofill()) {
1443 if (isVisible) {
1444 if (isInSet(mInvisibleTrackedIds, id)) {
1445 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
1446 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1447 }
1448 } else {
1449 if (isInSet(mVisibleTrackedIds, id)) {
1450 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
1451 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1452 }
1453 }
1454 }
1455
1456 if (mVisibleTrackedIds == null) {
Felipe Leme27e20222017-05-18 15:24:11 -07001457 if (sVerbose) {
1458 Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds);
1459 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001460 finishSessionLocked();
1461 }
1462 }
1463
1464 /**
1465 * Called once the client becomes visible.
1466 *
1467 * @see AutofillClient#isVisibleForAutofill()
1468 */
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001469 void onVisibleForAutofillLocked() {
1470 // The visibility of the views might have changed while the client was not be visible,
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001471 // hence update the visibility state for all views.
1472 AutofillClient client = getClientLocked();
1473 ArraySet<AutofillId> updatedVisibleTrackedIds = null;
1474 ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
1475 if (client != null) {
1476 if (mInvisibleTrackedIds != null) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001477 final ArrayList<AutofillId> orderedInvisibleIds =
1478 new ArrayList<>(mInvisibleTrackedIds);
1479 final boolean[] isVisible = client.getViewVisibility(
1480 getViewIds(orderedInvisibleIds));
1481
1482 final int numInvisibleTrackedIds = orderedInvisibleIds.size();
1483 for (int i = 0; i < numInvisibleTrackedIds; i++) {
1484 final AutofillId id = orderedInvisibleIds.get(i);
1485 if (isVisible[i]) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001486 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1487
Felipe Leme9f9ee252017-04-27 13:56:22 -07001488 if (sDebug) {
1489 Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001490 }
1491 } else {
1492 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1493 }
1494 }
1495 }
1496
1497 if (mVisibleTrackedIds != null) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001498 final ArrayList<AutofillId> orderedVisibleIds =
1499 new ArrayList<>(mVisibleTrackedIds);
1500 final boolean[] isVisible = client.getViewVisibility(
1501 getViewIds(orderedVisibleIds));
1502
1503 final int numVisibleTrackedIds = orderedVisibleIds.size();
1504 for (int i = 0; i < numVisibleTrackedIds; i++) {
1505 final AutofillId id = orderedVisibleIds.get(i);
1506
1507 if (isVisible[i]) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001508 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1509 } else {
1510 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1511
Felipe Leme9f9ee252017-04-27 13:56:22 -07001512 if (sDebug) {
1513 Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001514 }
1515 }
1516 }
1517 }
1518
1519 mInvisibleTrackedIds = updatedInvisibleTrackedIds;
1520 mVisibleTrackedIds = updatedVisibleTrackedIds;
1521 }
1522
1523 if (mVisibleTrackedIds == null) {
1524 finishSessionLocked();
1525 }
1526 }
1527 }
1528
1529 /**
Felipe Leme744976e2017-07-31 11:34:14 -07001530 * Callback for autofill related events.
Felipe Lemee6010f22017-03-03 11:19:51 -08001531 *
1532 * <p>Typically used for applications that display their own "auto-complete" views, so they can
Felipe Leme744976e2017-07-31 11:34:14 -07001533 * enable / disable such views when the autofill UI affordance is shown / hidden.
Felipe Lemee6010f22017-03-03 11:19:51 -08001534 */
1535 public abstract static class AutofillCallback {
1536
1537 /** @hide */
1538 @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
1539 @Retention(RetentionPolicy.SOURCE)
1540 public @interface AutofillEventType {}
1541
1542 /**
Felipe Leme744976e2017-07-31 11:34:14 -07001543 * The autofill input UI affordance associated with the view was shown.
Felipe Lemee6010f22017-03-03 11:19:51 -08001544 *
1545 * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
1546 * should be hidden upon receiving this event.
1547 */
1548 public static final int EVENT_INPUT_SHOWN = 1;
1549
1550 /**
Felipe Leme744976e2017-07-31 11:34:14 -07001551 * The autofill input UI affordance associated with the view was hidden.
Felipe Lemee6010f22017-03-03 11:19:51 -08001552 *
1553 * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
1554 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
1555 */
1556 public static final int EVENT_INPUT_HIDDEN = 2;
1557
1558 /**
Felipe Leme744976e2017-07-31 11:34:14 -07001559 * The autofill input UI affordance associated with the view isn't shown because
Felipe Leme24aae152017-03-15 12:33:01 -07001560 * autofill is not available.
1561 *
1562 * <p>If the view provides its own auto-complete UI affordance but was not displaying it
1563 * to avoid flickering, it could shown it upon receiving this event.
1564 */
1565 public static final int EVENT_INPUT_UNAVAILABLE = 3;
1566
1567 /**
Felipe Lemee6010f22017-03-03 11:19:51 -08001568 * Called after a change in the autofill state associated with a view.
1569 *
1570 * @param view view associated with the change.
1571 *
1572 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1573 */
Felipe Leme81f01d92017-03-16 17:13:25 -07001574 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
1575 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001576
1577 /**
1578 * Called after a change in the autofill state associated with a virtual view.
1579 *
1580 * @param view parent view associated with the change.
Felipe Leme6dcec872017-05-25 11:24:23 -07001581 * @param virtualId id identifying the virtual child inside the parent view.
Felipe Lemee6010f22017-03-03 11:19:51 -08001582 *
1583 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1584 */
Felipe Leme6dcec872017-05-25 11:24:23 -07001585 public void onAutofillEvent(@NonNull View view, int virtualId,
1586 @AutofillEventType int event) {
Felipe Leme81f01d92017-03-16 17:13:25 -07001587 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001588 }
1589
Felipe Leme640f30a2017-03-06 15:44:06 -08001590 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
1591 private final WeakReference<AutofillManager> mAfm;
Svet Ganov782043c2017-02-11 00:52:02 +00001592
Felipe Leme640f30a2017-03-06 15:44:06 -08001593 AutofillManagerClient(AutofillManager autofillManager) {
1594 mAfm = new WeakReference<>(autofillManager);
Svet Ganov782043c2017-02-11 00:52:02 +00001595 }
1596
1597 @Override
Svet Ganov48f10a22017-04-26 18:49:30 -07001598 public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001599 final AutofillManager afm = mAfm.get();
1600 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001601 afm.post(() -> afm.setState(enabled, resetSession, resetClient));
Svet Ganov782043c2017-02-11 00:52:02 +00001602 }
1603 }
1604
1605 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001606 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001607 final AutofillManager afm = mAfm.get();
1608 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001609 afm.post(() -> afm.autofill(sessionId, ids, values));
Svet Ganov782043c2017-02-11 00:52:02 +00001610 }
1611 }
1612
1613 @Override
Svetoslav Ganova9379d02017-05-09 17:40:24 -07001614 public void authenticate(int sessionId, int authenticationId, IntentSender intent,
1615 Intent fillInIntent) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001616 final AutofillManager afm = mAfm.get();
1617 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001618 afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
Svet Ganov782043c2017-02-11 00:52:02 +00001619 }
1620 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001621
1622 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001623 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1624 Rect anchorBounds, IAutofillWindowPresenter presenter) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001625 final AutofillManager afm = mAfm.get();
1626 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001627 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
1628 presenter));
Felipe Leme4753bb02017-03-22 20:24:00 -07001629 }
1630 }
1631
1632 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001633 public void requestHideFillUi(int sessionId, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001634 final AutofillManager afm = mAfm.get();
1635 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001636 afm.post(() -> afm.requestHideFillUi(id));
Felipe Leme4753bb02017-03-22 20:24:00 -07001637 }
1638 }
1639
1640 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001641 public void notifyNoFillUi(int sessionId, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001642 final AutofillManager afm = mAfm.get();
1643 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001644 afm.post(() -> afm.notifyNoFillUi(sessionId, id));
Felipe Lemee6010f22017-03-03 11:19:51 -08001645 }
1646 }
Svet Ganovc3d1c852017-04-12 22:34:28 -07001647
1648 @Override
1649 public void startIntentSender(IntentSender intentSender) {
1650 final AutofillManager afm = mAfm.get();
1651 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001652 afm.post(() -> {
Svet Ganovc3d1c852017-04-12 22:34:28 -07001653 try {
1654 afm.mContext.startIntentSender(intentSender, null, 0, 0, 0);
1655 } catch (IntentSender.SendIntentException e) {
1656 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
1657 }
1658 });
1659 }
1660 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001661
1662 @Override
Felipe Leme27e20222017-05-18 15:24:11 -07001663 public void setTrackedViews(int sessionId, AutofillId[] ids,
1664 boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001665 final AutofillManager afm = mAfm.get();
1666 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07001667 afm.post(() ->
Felipe Leme27e20222017-05-18 15:24:11 -07001668 afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001669 );
1670 }
1671 }
Svet Ganov782043c2017-02-11 00:52:02 +00001672 }
Felipe Leme3461d3c2017-01-19 08:54:55 -08001673}