blob: 6503a800acb708b855c0490dcb4878152f4a4518 [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
felipealfd3f8f62018-02-07 11:49:07 +010019import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
20import static android.view.autofill.Helper.sDebug;
21import static android.view.autofill.Helper.sVerbose;
Felipe Leme481db7c2019-03-14 15:50:12 -070022import static android.view.autofill.Helper.toList;
felipealfd3f8f62018-02-07 11:49:07 +010023
Svetoslav Ganov24c90452017-12-27 15:17:14 -080024import android.accessibilityservice.AccessibilityServiceInfo;
Felipe Lemee6010f22017-03-03 11:19:51 -080025import android.annotation.IntDef;
Philip P. Moltmann44611812017-02-23 12:52:46 -080026import android.annotation.NonNull;
Felipe Lemee6010f22017-03-03 11:19:51 -080027import android.annotation.Nullable;
Jeff Sharkey98af2e42018-02-16 10:14:57 -070028import android.annotation.RequiresFeature;
Felipe Leme559e21d2019-01-18 17:57:21 -080029import android.annotation.SystemApi;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060030import android.annotation.SystemService;
Felipe Leme2cab38c2019-01-11 10:39:14 -080031import android.annotation.TestApi;
Felipe Lemea4f39cd2019-02-19 15:08:59 -080032import android.content.AutofillOptions;
Felipe Leme17292d12017-10-24 14:03:10 -070033import android.content.ComponentName;
Felipe Leme3461d3c2017-01-19 08:54:55 -080034import android.content.Context;
Svet Ganov782043c2017-02-11 00:52:02 +000035import android.content.Intent;
36import android.content.IntentSender;
Svetoslav Ganov24c90452017-12-27 15:17:14 -080037import android.content.pm.PackageManager;
38import android.content.pm.ResolveInfo;
Felipe Leme3461d3c2017-01-19 08:54:55 -080039import android.graphics.Rect;
Felipe Leme4753bb02017-03-22 20:24:00 -070040import android.metrics.LogMaker;
Felipe Leme68b22222018-07-24 14:57:01 -070041import android.os.Build;
Svet Ganov782043c2017-02-11 00:52:02 +000042import android.os.Bundle;
Felipe Lemec24a56a2017-08-03 14:27:57 -070043import android.os.IBinder;
Svet Ganov782043c2017-02-11 00:52:02 +000044import android.os.Parcelable;
Felipe Leme3461d3c2017-01-19 08:54:55 -080045import android.os.RemoteException;
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -070046import android.service.autofill.AutofillService;
47import android.service.autofill.FillEventHistory;
Felipe Leme452886a2017-11-27 13:09:13 -080048import android.service.autofill.UserData;
Felipe Leme4753bb02017-03-22 20:24:00 -070049import android.util.ArrayMap;
Philip P. Moltmann494c3f52017-04-11 10:13:33 -070050import android.util.ArraySet;
Felipe Leme3461d3c2017-01-19 08:54:55 -080051import android.util.Log;
Felipe Lemece404982018-09-17 09:46:13 -070052import android.util.Slog;
Felipe Leme4753bb02017-03-22 20:24:00 -070053import android.util.SparseArray;
Svetoslav Ganov24c90452017-12-27 15:17:14 -080054import android.view.Choreographer;
Dake Gu6a20a192018-02-08 12:09:30 -080055import android.view.KeyEvent;
Felipe Leme3461d3c2017-01-19 08:54:55 -080056import android.view.View;
Svetoslav Ganov24c90452017-12-27 15:17:14 -080057import android.view.accessibility.AccessibilityEvent;
58import android.view.accessibility.AccessibilityManager;
59import android.view.accessibility.AccessibilityNodeInfo;
60import android.view.accessibility.AccessibilityNodeProvider;
61import android.view.accessibility.AccessibilityWindowInfo;
felipealfd3f8f62018-02-07 11:49:07 +010062
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070063import com.android.internal.annotations.GuardedBy;
Felipe Leme4753bb02017-03-22 20:24:00 -070064import com.android.internal.logging.MetricsLogger;
Felipe Lemeb22d6352017-09-08 20:03:53 -070065import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Felipe Lemed4e52282018-06-18 13:56:38 -070066import com.android.internal.os.IResultReceiver;
Svetoslav Ganov24c90452017-12-27 15:17:14 -080067import com.android.internal.util.ArrayUtils;
Felipe Leme637e05e2017-12-06 12:09:37 -080068import com.android.internal.util.Preconditions;
Felipe Lemec0c15a32019-01-08 10:53:33 -080069import com.android.internal.util.SyncResultReceiver;
felipealfd3f8f62018-02-07 11:49:07 +010070
Svetoslav Ganov24c90452017-12-27 15:17:14 -080071import org.xmlpull.v1.XmlPullParserException;
Felipe Leme4753bb02017-03-22 20:24:00 -070072
Svetoslav Ganov24c90452017-12-27 15:17:14 -080073import java.io.IOException;
Felipe Lemec24a56a2017-08-03 14:27:57 -070074import java.io.PrintWriter;
Felipe Lemee6010f22017-03-03 11:19:51 -080075import java.lang.annotation.Retention;
76import java.lang.annotation.RetentionPolicy;
Svet Ganov782043c2017-02-11 00:52:02 +000077import java.lang.ref.WeakReference;
Philip P. Moltmann134cee22017-05-06 11:28:38 -070078import java.util.ArrayList;
Felipe Lemebc055b02018-01-05 17:04:10 -080079import java.util.Arrays;
Felipe Leme27f45732017-12-22 09:05:22 -080080import java.util.Collections;
Svet Ganov782043c2017-02-11 00:52:02 +000081import java.util.List;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -070082import java.util.Objects;
Felipe Leme7a3c9f52019-02-13 16:32:49 -080083import java.util.Set;
Felipe Lemec0c15a32019-01-08 10:53:33 -080084
Felipe Leme67e62092018-02-15 14:47:31 -080085//TODO: use java.lang.ref.Cleaner once Android supports Java 9
felipealfd3f8f62018-02-07 11:49:07 +010086import sun.misc.Cleaner;
Svetoslav Ganov24c90452017-12-27 15:17:14 -080087
Felipe Leme3461d3c2017-01-19 08:54:55 -080088/**
Laura Davis43e75d92018-08-10 15:46:06 -070089 * <p>The {@link AutofillManager} class provides ways for apps and custom views to
90 * integrate with the Autofill Framework lifecycle.
91 *
92 * <p>To learn about using Autofill in your app, read
93 * the <a href="/guide/topics/text/autofill">Autofill Framework</a> guides.
94 *
95 * <h3 id="autofill-lifecycle">Autofill lifecycle</h3>
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070096 *
Felipe Leme744976e2017-07-31 11:34:14 -070097 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an
Laura Davis43e75d92018-08-10 15:46:06 -070098 * activity context. The autofill context is created when one of the following methods is called for
Felipe Leme744976e2017-07-31 11:34:14 -070099 * the first time in an activity context, and the current user has an enabled autofill service:
100 *
101 * <ul>
102 * <li>{@link #notifyViewEntered(View)}
103 * <li>{@link #notifyViewEntered(View, int, Rect)}
104 * <li>{@link #requestAutofill(View)}
105 * </ul>
106 *
Laura Davis43e75d92018-08-10 15:46:06 -0700107 * <p>Typically, the context is automatically created when the first view of the activity is
Felipe Leme744976e2017-07-31 11:34:14 -0700108 * focused because {@code View.onFocusChanged()} indirectly calls
109 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to
110 * explicitly create it (for example, a custom view developer could offer a contextual menu action
111 * in a text-field view to let users manually request autofill).
112 *
113 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure}
114 * that represents the view hierarchy by calling
115 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views
116 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in
117 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and
118 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in
119 * the hierarchy.
120 *
121 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which
122 * parses it looking for views that can be autofilled. If the service finds such views, it returns
123 * a data structure to the Android System containing the following optional info:
124 *
125 * <ul>
126 * <li>Datasets used to autofill subsets of views in the activity.
127 * <li>Id of views that the service can save their values for future autofilling.
128 * </ul>
129 *
130 * <p>When the service returns datasets, the Android System displays an autofill dataset picker
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700131 * UI associated with the view, when the view is focused on and is part of a dataset.
132 * The application can be notified when the UI is shown by registering an
Felipe Leme744976e2017-07-31 11:34:14 -0700133 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700134 * selects a dataset from the UI, all views present in the dataset are autofilled, through
Felipe Leme744976e2017-07-31 11:34:14 -0700135 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
136 *
137 * <p>When the service returns ids of savable views, the Android System keeps track of changes
138 * made to these views, so they can be used to determine if the autofill save UI is shown later.
139 *
140 * <p>The context is then finished when one of the following occurs:
141 *
142 * <ul>
143 * <li>{@link #commit()} is called or all savable views are gone.
144 * <li>{@link #cancel()} is called.
145 * </ul>
146 *
147 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700148 * shows an autofill save UI if the value of savable views have changed. If the user selects the
Felipe Leme744976e2017-07-31 11:34:14 -0700149 * option to Save, the current value of the views is then sent to the autofill service.
150 *
Laura Davis43e75d92018-08-10 15:46:06 -0700151 * <h3 id="additional-notes">Additional notes</h3>
152 *
153 * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800154 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600155@SystemService(Context.AUTOFILL_MANAGER_SERVICE)
Jeff Sharkey98af2e42018-02-16 10:14:57 -0700156@RequiresFeature(PackageManager.FEATURE_AUTOFILL)
Felipe Leme640f30a2017-03-06 15:44:06 -0800157public final class AutofillManager {
Felipe Leme3461d3c2017-01-19 08:54:55 -0800158
Felipe Leme640f30a2017-03-06 15:44:06 -0800159 private static final String TAG = "AutofillManager";
Felipe Leme3461d3c2017-01-19 08:54:55 -0800160
Svet Ganov782043c2017-02-11 00:52:02 +0000161 /**
162 * Intent extra: The assist structure which captures the filled screen.
Felipe Leme7320ca92017-03-29 15:09:54 -0700163 *
Svet Ganov782043c2017-02-11 00:52:02 +0000164 * <p>
165 * Type: {@link android.app.assist.AssistStructure}
Svet Ganov782043c2017-02-11 00:52:02 +0000166 */
167 public static final String EXTRA_ASSIST_STRUCTURE =
168 "android.view.autofill.extra.ASSIST_STRUCTURE";
169
170 /**
171 * Intent extra: The result of an authentication operation. It is
172 * either a fully populated {@link android.service.autofill.FillResponse}
173 * or a fully populated {@link android.service.autofill.Dataset} if
174 * a response or a dataset is being authenticated respectively.
175 *
176 * <p>
177 * Type: {@link android.service.autofill.FillResponse} or a
178 * {@link android.service.autofill.Dataset}
Svet Ganov782043c2017-02-11 00:52:02 +0000179 */
180 public static final String EXTRA_AUTHENTICATION_RESULT =
181 "android.view.autofill.extra.AUTHENTICATION_RESULT";
182
Felipe Leme7320ca92017-03-29 15:09:54 -0700183 /**
184 * Intent extra: The optional extras provided by the
185 * {@link android.service.autofill.AutofillService}.
186 *
187 * <p>For example, when the service responds to a {@link
188 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
189 * a {@code FillResponse} that requires authentication, the Intent that launches the
190 * service authentication will contain the Bundle set by
Felipe Leme49e96962017-04-20 15:46:18 -0700191 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
Felipe Leme7320ca92017-03-29 15:09:54 -0700192 *
Felipe Lemea9372382017-10-09 14:42:36 -0700193 * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
194 * can also add this bundle to the {@link Intent} set as the
195 * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
196 * so the bundle can be recovered later on
197 * {@link android.service.autofill.SaveRequest#getClientState()}.
198 *
Felipe Leme7320ca92017-03-29 15:09:54 -0700199 * <p>
200 * Type: {@link android.os.Bundle}
201 */
Felipe Leme37a440fd2017-04-28 10:50:20 -0700202 public static final String EXTRA_CLIENT_STATE =
Jeff Sharkey000ce802017-04-29 13:13:27 -0600203 "android.view.autofill.extra.CLIENT_STATE";
Felipe Leme7320ca92017-03-29 15:09:54 -0700204
Felipe Lemec24a56a2017-08-03 14:27:57 -0700205 /** @hide */
206 public static final String EXTRA_RESTORE_SESSION_TOKEN =
207 "android.view.autofill.extra.RESTORE_SESSION_TOKEN";
208
Felipe Leme284ad1c2018-11-15 18:16:12 -0800209 /**
210 * Internal extra used to pass a binder to the {@link IAugmentedAutofillManagerClient}.
211 *
212 * @hide
213 */
214 public static final String EXTRA_AUGMENTED_AUTOFILL_CLIENT =
215 "android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT";
216
Felipe Lemec24a56a2017-08-03 14:27:57 -0700217 private static final String SESSION_ID_TAG = "android:sessionId";
218 private static final String STATE_TAG = "android:state";
219 private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
220
Felipe Leme0aa4c502017-04-26 12:36:01 -0700221 /** @hide */ public static final int ACTION_START_SESSION = 1;
222 /** @hide */ public static final int ACTION_VIEW_ENTERED = 2;
223 /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
224 /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800225
Felipe Leme68b22222018-07-24 14:57:01 -0700226 /** @hide */ public static final int NO_LOGGING = 0;
Felipe Leme9f9ee252017-04-27 13:56:22 -0700227 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
228 /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
229 /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
Felipe Lemedf30f272019-03-19 13:47:48 -0700230 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
Felipe Lemeadb34f52019-03-15 16:24:26 -0700231
232 /** @hide */ public static final int FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
233
Felipe Leme68b22222018-07-24 14:57:01 -0700234 /** @hide */
235 public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE
236 ? AutofillManager.FLAG_ADD_CLIENT_DEBUG
237 : AutofillManager.NO_LOGGING;
238
239 /** @hide */
240 public static final int DEFAULT_MAX_PARTITIONS_SIZE = 10;
Felipe Leme9f9ee252017-04-27 13:56:22 -0700241
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700242 /** Which bits in an authentication id are used for the dataset id */
243 private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
244 /** How many bits in an authentication id are used for the dataset id */
245 private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
246 /** @hide The index for an undefined data set */
247 public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
248
249 /**
Felipe Lemec24a56a2017-08-03 14:27:57 -0700250 * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI.
251 *
252 * @hide
253 */
254 public static final int PENDING_UI_OPERATION_CANCEL = 1;
255
256 /**
257 * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI.
258 *
259 * @hide
260 */
261 public static final int PENDING_UI_OPERATION_RESTORE = 2;
262
263 /**
264 * Initial state of the autofill context, set when there is no session (i.e., when
265 * {@link #mSessionId} is {@link #NO_SESSION}).
266 *
Felipe Lemec7b45292017-09-19 09:06:20 -0700267 * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to
268 * the server.
269 *
Felipe Lemec24a56a2017-08-03 14:27:57 -0700270 * @hide
271 */
Felipe Lemec7b45292017-09-19 09:06:20 -0700272 public static final int STATE_UNKNOWN = 0;
Felipe Lemec24a56a2017-08-03 14:27:57 -0700273
274 /**
275 * State where the autofill context hasn't been {@link #commit() finished} nor
276 * {@link #cancel() canceled} yet.
277 *
278 * @hide
279 */
Felipe Lemec7b45292017-09-19 09:06:20 -0700280 public static final int STATE_ACTIVE = 1;
281
282 /**
283 * State where the autofill context was finished by the server because the autofill
Felipe Leme17292d12017-10-24 14:03:10 -0700284 * service could not autofill the activity.
Felipe Lemec7b45292017-09-19 09:06:20 -0700285 *
286 * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
287 * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
288 *
289 * @hide
290 */
291 public static final int STATE_FINISHED = 2;
Felipe Lemec24a56a2017-08-03 14:27:57 -0700292
293 /**
294 * State where the autofill context has been {@link #commit() finished} but the server still has
295 * a session because the Save UI hasn't been dismissed yet.
296 *
297 * @hide
298 */
Felipe Lemec7b45292017-09-19 09:06:20 -0700299 public static final int STATE_SHOWING_SAVE_UI = 3;
Felipe Lemec24a56a2017-08-03 14:27:57 -0700300
301 /**
Felipe Leme17292d12017-10-24 14:03:10 -0700302 * State where the autofill is disabled because the service cannot autofill the activity at all.
303 *
304 * <p>In this state, every call is ignored, even {@link #requestAutofill(View)}
305 * (and {@link #requestAutofill(View, int, Rect)}).
306 *
307 * @hide
308 */
309 public static final int STATE_DISABLED_BY_SERVICE = 4;
310
311 /**
Felipe Leme0c8ce322018-03-23 13:54:22 -0700312 * Same as {@link #STATE_UNKNOWN}, but used on
Felipe Lemeadb34f52019-03-15 16:24:26 -0700313 * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished
314 * because the URL bar changed on client mode
Felipe Leme0c8ce322018-03-23 13:54:22 -0700315 *
316 * @hide
317 */
318 public static final int STATE_UNKNOWN_COMPAT_MODE = 5;
319
Felipe Lemed9dc9542018-09-19 11:54:28 -0700320 /**
321 * Same as {@link #STATE_UNKNOWN}, but used on
Felipe Lemeadb34f52019-03-15 16:24:26 -0700322 * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished
323 * because the service failed to fullfil a request.
Felipe Lemed9dc9542018-09-19 11:54:28 -0700324 *
325 * @hide
326 */
327 public static final int STATE_UNKNOWN_FAILED = 6;
Felipe Leme0c8ce322018-03-23 13:54:22 -0700328
329 /**
Felipe Lemebc055b02018-01-05 17:04:10 -0800330 * Timeout in ms for calls to the field classification service.
331 * @hide
332 */
333 public static final int FC_SERVICE_TIMEOUT = 5000;
334
335 /**
Felipe Lemec0c15a32019-01-08 10:53:33 -0800336 * Timeout for calls to system_server.
337 */
338 private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
339
340 /**
Felipe Leme2cab38c2019-01-11 10:39:14 -0800341 * @hide
342 */
343 @TestApi
344 public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
345
346 /**
Felipe Lemecc510272019-02-07 14:56:27 -0800347 * Disables Augmented Autofill.
348 *
349 * @hide
350 */
351 @TestApi
352 public static final int FLAG_SMART_SUGGESTION_OFF = 0x0;
353
354 /**
Felipe Leme559e21d2019-01-18 17:57:21 -0800355 * Displays the Augment Autofill window using the same mechanism (such as a popup-window
356 * attached to the focused view) as the standard autofill.
357 *
358 * @hide
359 */
360 @TestApi
361 public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1;
362
Felipe Leme559e21d2019-01-18 17:57:21 -0800363 /** @hide */
Felipe Lemecc510272019-02-07 14:56:27 -0800364 @IntDef(flag = false, value = { FLAG_SMART_SUGGESTION_OFF, FLAG_SMART_SUGGESTION_SYSTEM })
Felipe Leme559e21d2019-01-18 17:57:21 -0800365 @Retention(RetentionPolicy.SOURCE)
366 public @interface SmartSuggestionMode {}
367
368 /**
Felipe Lemecc510272019-02-07 14:56:27 -0800369 * {@code DeviceConfig} property used to set which Smart Suggestion modes for Augmented Autofill
370 * are available.
Felipe Leme7841d022019-02-07 13:02:18 -0800371 *
372 * @hide
373 */
374 @TestApi
375 public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES =
376 "smart_suggestion_supported_modes";
377
Felipe Lemea8209102019-02-21 18:01:47 -0800378 /**
379 * Sets how long (in ms) the augmented autofill service is bound while idle.
380 *
381 * <p>Use {@code 0} to keep it permanently bound.
382 *
383 * @hide
384 */
385 public static final String DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT =
386 "augmented_service_idle_unbind_timeout";
387
388 /**
389 * Sets how long (in ms) the augmented autofill service request is killed if not replied.
390 *
391 * @hide
392 */
393 public static final String DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT =
394 "augmented_service_request_timeout";
395
Felipe Leme80e7bf12019-02-14 10:56:52 -0800396 /** @hide */
397 public static final int RESULT_OK = 0;
398 /** @hide */
399 public static final int RESULT_CODE_NOT_SERVICE = -1;
400
Felipe Leme7841d022019-02-07 13:02:18 -0800401 /**
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700402 * Makes an authentication id from a request id and a dataset id.
403 *
404 * @param requestId The request id.
405 * @param datasetId The dataset id.
406 * @return The authentication id.
407 * @hide
408 */
409 public static int makeAuthenticationId(int requestId, int datasetId) {
410 return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT)
411 | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK);
412 }
413
414 /**
415 * Gets the request id from an authentication id.
416 *
417 * @param authRequestId The authentication id.
418 * @return The request id.
419 * @hide
420 */
421 public static int getRequestIdFromAuthenticationId(int authRequestId) {
422 return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT);
423 }
424
425 /**
426 * Gets the dataset id from an authentication id.
427 *
428 * @param authRequestId The authentication id.
429 * @return The dataset id.
430 * @hide
431 */
432 public static int getDatasetIdFromAuthenticationId(int authRequestId) {
433 return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK);
434 }
435
Felipe Leme4753bb02017-03-22 20:24:00 -0700436 private final MetricsLogger mMetricsLogger = new MetricsLogger();
Svet Ganov782043c2017-02-11 00:52:02 +0000437
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700438 /**
439 * There is currently no session running.
440 * {@hide}
441 */
Felipe Lemeadb34f52019-03-15 16:24:26 -0700442 public static final int NO_SESSION = Integer.MAX_VALUE;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700443
Svet Ganov782043c2017-02-11 00:52:02 +0000444 private final IAutoFillManager mService;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700445
446 private final Object mLock = new Object();
447
448 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000449 private IAutoFillManagerClient mServiceClient;
450
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700451 @GuardedBy("mLock")
Koji Fukuiccec6a62017-10-18 17:48:40 +0900452 private Cleaner mServiceClientCleaner;
453
454 @GuardedBy("mLock")
Felipe Leme284ad1c2018-11-15 18:16:12 -0800455 private IAugmentedAutofillManagerClient mAugmentedAutofillServiceClient;
456
457 @GuardedBy("mLock")
Felipe Lemee6010f22017-03-03 11:19:51 -0800458 private AutofillCallback mCallback;
459
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700460 private final Context mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000461
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700462 @GuardedBy("mLock")
463 private int mSessionId = NO_SESSION;
464
465 @GuardedBy("mLock")
Felipe Lemec24a56a2017-08-03 14:27:57 -0700466 private int mState = STATE_UNKNOWN;
467
468 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000469 private boolean mEnabled;
470
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700471 /** If a view changes to this mapping the autofill operation was successful */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700472 @GuardedBy("mLock")
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700473 @Nullable private ParcelableMap mLastAutofilledData;
474
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700475 /** If view tracking is enabled, contains the tracking state */
476 @GuardedBy("mLock")
477 @Nullable private TrackedViews mTrackedViews;
478
Felipe Leme27e20222017-05-18 15:24:11 -0700479 /** Views that are only tracked because they are fillable and could be anchoring the UI. */
480 @GuardedBy("mLock")
481 @Nullable private ArraySet<AutofillId> mFillableIds;
482
Dake Gub0fa3782018-02-26 12:25:14 -0800483 /** id of last requested autofill ui */
484 @Nullable private AutofillId mIdShownFillUi;
485
Felipe Leme9a15c0f2018-02-14 17:26:46 -0800486 /**
487 * Views that were already "entered" - if they're entered again when the session is not active,
488 * they're ignored
489 * */
490 @GuardedBy("mLock")
491 @Nullable private ArraySet<AutofillId> mEnteredIds;
492
Felipe Lemea7de4022019-02-19 17:16:45 -0800493 /**
494 * Views that were otherwised not important for autofill but triggered a session because the
495 * context is whitelisted for augmented autofill.
496 */
497 @GuardedBy("mLock")
498 @Nullable private Set<AutofillId> mEnteredForAugmentedAutofillIds;
499
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700500 /** If set, session is commited when the field is clicked. */
501 @GuardedBy("mLock")
502 @Nullable private AutofillId mSaveTriggerId;
503
Dake Gu67decfa2017-12-27 11:48:08 -0800504 /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */
505 @GuardedBy("mLock")
506 private boolean mOnInvisibleCalled;
507
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700508 /** If set, session is commited when the activity is finished; otherwise session is canceled. */
509 @GuardedBy("mLock")
510 private boolean mSaveOnFinish;
511
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800512 /** If compatibility mode is enabled - this is a bridge to interact with a11y */
513 @GuardedBy("mLock")
514 private CompatibilityBridge mCompatibilityBridge;
515
Felipe Lemea4f39cd2019-02-19 15:08:59 -0800516 @Nullable
517 private final AutofillOptions mOptions;
518
Felipe Lemeadb34f52019-03-15 16:24:26 -0700519 /** When set, session is only used for augmented autofill requests. */
520 @GuardedBy("mLock")
521 private boolean mForAugmentedAutofillOnly;
522
Felipe Lemedf30f272019-03-19 13:47:48 -0700523 /**
524 * When set, standard autofill is enabled, but sessions can still be created for augmented
525 * autofill only.
526 */
527 @GuardedBy("mLock")
528 private boolean mEnabledForAugmentedAutofillOnly;
529
Svet Ganov782043c2017-02-11 00:52:02 +0000530 /** @hide */
Felipe Leme640f30a2017-03-06 15:44:06 -0800531 public interface AutofillClient {
Svet Ganov782043c2017-02-11 00:52:02 +0000532 /**
Svet Ganov782043c2017-02-11 00:52:02 +0000533 * Asks the client to start an authentication flow.
534 *
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700535 * @param authenticationId A unique id of the authentication operation.
Svet Ganov782043c2017-02-11 00:52:02 +0000536 * @param intent The authentication intent.
537 * @param fillInIntent The authentication fill-in intent.
538 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800539 void autofillClientAuthenticate(int authenticationId, IntentSender intent,
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700540 Intent fillInIntent);
Svet Ganov17db9dc2017-02-21 19:54:31 -0800541
542 /**
543 * Tells the client this manager has state to be reset.
544 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800545 void autofillClientResetableStateAvailable();
Felipe Leme4753bb02017-03-22 20:24:00 -0700546
547 /**
548 * Request showing the autofill UI.
549 *
550 * @param anchor The real view the UI needs to anchor to.
551 * @param width The width of the fill UI content.
552 * @param height The height of the fill UI content.
553 * @param virtualBounds The bounds of the virtual decendant of the anchor.
554 * @param presenter The presenter that controls the fill UI window.
555 * @return Whether the UI was shown.
556 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800557 boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height,
Felipe Leme4753bb02017-03-22 20:24:00 -0700558 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
559
560 /**
Dake Gu6a20a192018-02-08 12:09:30 -0800561 * Dispatch unhandled keyevent from Autofill window
562 * @param anchor The real view the UI needs to anchor to.
563 * @param keyEvent Unhandled KeyEvent from autofill window.
564 */
565 void autofillClientDispatchUnhandledKey(@NonNull View anchor, @NonNull KeyEvent keyEvent);
566
567 /**
Felipe Leme4753bb02017-03-22 20:24:00 -0700568 * Request hiding the autofill UI.
569 *
570 * @return Whether the UI was hidden.
571 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800572 boolean autofillClientRequestHideFillUi();
573
574 /**
575 * Gets whether the fill UI is currenlty being shown.
576 *
577 * @return Whether the fill UI is currently being shown
578 */
579 boolean autofillClientIsFillUiShowing();
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700580
581 /**
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700582 * Checks if views are currently attached and visible.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700583 *
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700584 * @return And array with {@code true} iff the view is attached or visible
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700585 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800586 @NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700587
588 /**
589 * Checks is the client is currently visible as understood by autofill.
590 *
591 * @return {@code true} if the client is currently visible
592 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800593 boolean autofillClientIsVisibleForAutofill();
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700594
595 /**
Dake Gu67decfa2017-12-27 11:48:08 -0800596 * Client might disable enter/exit event e.g. when activity is paused.
597 */
598 boolean isDisablingEnterExitEventForAutofill();
599
600 /**
Felipe Leme27e20222017-05-18 15:24:11 -0700601 * Finds views by traversing the hierarchies of the client.
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700602 *
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800603 * @param autofillIds The autofill ids of the views to find
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700604 *
Felipe Leme27e20222017-05-18 15:24:11 -0700605 * @return And array containing the views (empty if no views found).
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700606 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800607 @NonNull View[] autofillClientFindViewsByAutofillIdTraversal(
608 @NonNull AutofillId[] autofillIds);
Felipe Leme27e20222017-05-18 15:24:11 -0700609
610 /**
611 * Finds a view by traversing the hierarchies of the client.
612 *
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800613 * @param autofillId The autofill id of the views to find
Felipe Leme27e20222017-05-18 15:24:11 -0700614 *
615 * @return The view, or {@code null} if not found
616 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800617 @Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId);
618
619 /**
620 * Finds a view by a11y id in a given client window.
621 *
622 * @param viewId The accessibility id of the views to find
623 * @param windowId The accessibility window id where to search
624 *
625 * @return The view, or {@code null} if not found
626 */
627 @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);
Felipe Leme9876a6f2017-05-30 15:47:28 -0700628
629 /**
630 * Runs the specified action on the UI thread.
631 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800632 void autofillClientRunOnUiThread(Runnable action);
Felipe Leme17292d12017-10-24 14:03:10 -0700633
634 /**
635 * Gets the complete component name of this client.
636 */
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800637 ComponentName autofillClientGetComponentName();
638
639 /**
640 * Gets the activity token
641 */
642 @Nullable IBinder autofillClientGetActivityToken();
643
644 /**
645 * @return Whether compatibility mode is enabled.
646 */
Felipe Leme42b97932018-02-20 13:04:31 -0800647 boolean autofillClientIsCompatibilityModeEnabled();
648
649 /**
650 * Gets the next unique autofill ID.
651 *
652 * <p>Typically used to manage views whose content is recycled - see
653 * {@link View#setAutofillId(AutofillId)} for more info.
654 *
655 * @return An ID that is unique in the activity.
656 */
657 @Nullable AutofillId autofillClientGetNextAutofillId();
Svet Ganov782043c2017-02-11 00:52:02 +0000658 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800659
660 /**
661 * @hide
662 */
Felipe Leme640f30a2017-03-06 15:44:06 -0800663 public AutofillManager(Context context, IAutoFillManager service) {
Felipe Leme637e05e2017-12-06 12:09:37 -0800664 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Leme3461d3c2017-01-19 08:54:55 -0800665 mService = service;
Felipe Lemea4f39cd2019-02-19 15:08:59 -0800666 mOptions = context.getAutofillOptions();
667
668 if (mOptions != null) {
669 sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
670 sVerbose = (mOptions.loggingLevel & FLAG_ADD_CLIENT_VERBOSE) != 0;
671 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800672 }
673
674 /**
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800675 * @hide
676 */
677 public void enableCompatibilityMode() {
678 synchronized (mLock) {
679 // The accessibility manager is a singleton so we may need to plug
680 // different bridge based on which activity is currently focused
681 // in the current process. Since compat would be rarely used, just
682 // create and register a new instance every time.
Felipe Lemece404982018-09-17 09:46:13 -0700683 if (sDebug) {
684 Slog.d(TAG, "creating CompatibilityBridge for " + mContext);
685 }
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800686 mCompatibilityBridge = new CompatibilityBridge();
687 }
688 }
689
690 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700691 * Restore state after activity lifecycle
692 *
693 * @param savedInstanceState The state to be restored
694 *
695 * {@hide}
696 */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700697 public void onCreate(Bundle savedInstanceState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700698 if (!hasAutofillFeature()) {
699 return;
700 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700701 synchronized (mLock) {
702 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
703
Felipe Lemec24a56a2017-08-03 14:27:57 -0700704 if (isActiveLocked()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700705 Log.w(TAG, "New session was started before onCreate()");
706 return;
707 }
708
709 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
Felipe Lemec24a56a2017-08-03 14:27:57 -0700710 mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700711
712 if (mSessionId != NO_SESSION) {
713 ensureServiceClientAddedIfNeededLocked();
714
Felipe Leme637e05e2017-12-06 12:09:37 -0800715 final AutofillClient client = getClient();
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700716 if (client != null) {
Felipe Lemec0c15a32019-01-08 10:53:33 -0800717 final SyncResultReceiver receiver = new SyncResultReceiver(
718 SYNC_CALLS_TIMEOUT_MS);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700719 try {
Felipe Lemed4e52282018-06-18 13:56:38 -0700720 mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(),
721 mServiceClient.asBinder(), receiver);
722 final boolean sessionWasRestored = receiver.getIntResult() == 1;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700723
724 if (!sessionWasRestored) {
725 Log.w(TAG, "Session " + mSessionId + " could not be restored");
726 mSessionId = NO_SESSION;
Felipe Lemec24a56a2017-08-03 14:27:57 -0700727 mState = STATE_UNKNOWN;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700728 } else {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700729 if (sDebug) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700730 Log.d(TAG, "session " + mSessionId + " was restored");
731 }
732
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800733 client.autofillClientResetableStateAvailable();
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700734 }
735 } catch (RemoteException e) {
736 Log.e(TAG, "Could not figure out if there was an autofill session", e);
737 }
738 }
739 }
740 }
741 }
742
743 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700744 * Called once the client becomes visible.
745 *
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800746 * @see AutofillClient#autofillClientIsVisibleForAutofill()
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700747 *
748 * {@hide}
749 */
750 public void onVisibleForAutofill() {
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800751 // This gets called when the client just got visible at which point the visibility
752 // of the tracked views may not have been computed (due to a pending layout, etc).
753 // While generally we have no way to know when the UI has settled. We will evaluate
754 // the tracked views state at the end of next frame to guarantee that everything
755 // that may need to be laid out is laid out.
756 Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
757 synchronized (mLock) {
758 if (mEnabled && isActiveLocked() && mTrackedViews != null) {
759 mTrackedViews.onVisibleForAutofillChangedLocked();
760 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700761 }
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800762 }, null);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700763 }
764
765 /**
Dake Gu67decfa2017-12-27 11:48:08 -0800766 * Called once the client becomes invisible.
767 *
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800768 * @see AutofillClient#autofillClientIsVisibleForAutofill()
Dake Gu67decfa2017-12-27 11:48:08 -0800769 *
770 * {@hide}
771 */
772 public void onInvisibleForAutofill() {
773 synchronized (mLock) {
774 mOnInvisibleCalled = true;
775 }
776 }
777
778 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700779 * Save state before activity lifecycle
780 *
781 * @param outState Place to store the state
782 *
783 * {@hide}
784 */
785 public void onSaveInstanceState(Bundle outState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700786 if (!hasAutofillFeature()) {
787 return;
788 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700789 synchronized (mLock) {
790 if (mSessionId != NO_SESSION) {
791 outState.putInt(SESSION_ID_TAG, mSessionId);
792 }
Felipe Lemec24a56a2017-08-03 14:27:57 -0700793 if (mState != STATE_UNKNOWN) {
794 outState.putInt(STATE_TAG, mState);
795 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700796 if (mLastAutofilledData != null) {
797 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
798 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700799 }
800 }
801
802 /**
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800803 * @hide
804 */
Andreas Gampe3f24e692018-02-05 13:24:28 -0800805 @GuardedBy("mLock")
felipealfd3f8f62018-02-07 11:49:07 +0100806 public boolean isCompatibilityModeEnabledLocked() {
807 return mCompatibilityBridge != null;
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800808 }
809
810 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700811 * Checks whether autofill is enabled for the current user.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700812 *
813 * <p>Typically used to determine whether the option to explicitly request autofill should
814 * be offered - see {@link #requestAutofill(View)}.
815 *
816 * @return whether autofill is enabled for the current user.
817 */
818 public boolean isEnabled() {
Felipe Leme3103c632017-12-18 15:05:14 -0800819 if (!hasAutofillFeature()) {
Svet Ganov43574b02017-04-12 09:25:20 -0700820 return false;
821 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700822 synchronized (mLock) {
Felipe Leme3103c632017-12-18 15:05:14 -0800823 if (isDisabledByServiceLocked()) {
824 return false;
825 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700826 ensureServiceClientAddedIfNeededLocked();
827 return mEnabled;
828 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700829 }
830
831 /**
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700832 * Should always be called from {@link AutofillService#getFillEventHistory()}.
833 *
834 * @hide
835 */
836 @Nullable public FillEventHistory getFillEventHistory() {
837 try {
Felipe Lemec0c15a32019-01-08 10:53:33 -0800838 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemed4e52282018-06-18 13:56:38 -0700839 mService.getFillEventHistory(receiver);
Felipe Lemec0c15a32019-01-08 10:53:33 -0800840 return receiver.getParcelableResult();
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700841 } catch (RemoteException e) {
842 e.rethrowFromSystemServer();
843 return null;
844 }
845 }
846
847 /**
Felipe Leme2ac463e2017-03-13 14:06:25 -0700848 * Explicitly requests a new autofill context.
849 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700850 * <p>Normally, the autofill context is automatically started if necessary when
851 * {@link #notifyViewEntered(View)} is called, but this method should be used in the
852 * cases where it must be explicitly started. For example, when the view offers an AUTOFILL
853 * option on its contextual overflow menu, and the user selects it.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700854 *
855 * @param view view requesting the new autofill context.
856 */
857 public void requestAutofill(@NonNull View view) {
Felipe Lemed1146422017-04-26 10:17:05 -0700858 notifyViewEntered(view, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700859 }
860
861 /**
862 * Explicitly requests a new autofill context for virtual views.
863 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700864 * <p>Normally, the autofill context is automatically started if necessary when
865 * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the
866 * cases where it must be explicitly started. For example, when the virtual view offers an
867 * AUTOFILL option on its contextual overflow menu, and the user selects it.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700868 *
Felipe Leme6dcec872017-05-25 11:24:23 -0700869 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
870 * parent view uses {@code bounds} to draw the virtual view inside its Canvas,
871 * the absolute bounds could be calculated by:
872 *
873 * <pre class="prettyprint">
874 * int offset[] = new int[2];
875 * getLocationOnScreen(offset);
876 * Rect absBounds = new Rect(bounds.left + offset[0],
877 * bounds.top + offset[1],
878 * bounds.right + offset[0], bounds.bottom + offset[1]);
879 * </pre>
880 *
881 * @param view the virtual view parent.
882 * @param virtualId id identifying the virtual child inside the parent view.
883 * @param absBounds absolute boundaries of the virtual view in the screen.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700884 */
Felipe Leme6dcec872017-05-25 11:24:23 -0700885 public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
886 notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700887 }
888
Felipe Leme2ac463e2017-03-13 14:06:25 -0700889 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700890 * Called when a {@link View} that supports autofill is entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800891 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700892 * @param view {@link View} that was entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800893 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700894 public void notifyViewEntered(@NonNull View view) {
Felipe Lemed1146422017-04-26 10:17:05 -0700895 notifyViewEntered(view, 0);
896 }
897
Andreas Gampe3f24e692018-02-05 13:24:28 -0800898 @GuardedBy("mLock")
Felipe Leme9a15c0f2018-02-14 17:26:46 -0800899 private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
Felipe Leme3103c632017-12-18 15:05:14 -0800900 if (isDisabledByServiceLocked()) {
Felipe Leme17292d12017-10-24 14:03:10 -0700901 if (sVerbose) {
Felipe Leme9a15c0f2018-02-14 17:26:46 -0800902 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id
903 + ") on state " + getStateAsStringLocked() + " because disabled by svc");
Felipe Leme17292d12017-10-24 14:03:10 -0700904 }
905 return true;
906 }
Felipe Leme9a15c0f2018-02-14 17:26:46 -0800907 if (isFinishedLocked()) {
908 // Session already finished: ignore if automatic request and view already entered
909 if ((flags & FLAG_MANUAL_REQUEST) == 0 && mEnteredIds != null
910 && mEnteredIds.contains(id)) {
911 if (sVerbose) {
912 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id
913 + ") on state " + getStateAsStringLocked()
914 + " because view was already entered: " + mEnteredIds);
915 }
916 return true;
917 }
918 }
Felipe Leme17292d12017-10-24 14:03:10 -0700919 return false;
920 }
921
Dake Gu67decfa2017-12-27 11:48:08 -0800922 private boolean isClientVisibleForAutofillLocked() {
923 final AutofillClient client = getClient();
Svetoslav Ganov24c90452017-12-27 15:17:14 -0800924 return client != null && client.autofillClientIsVisibleForAutofill();
Dake Gu67decfa2017-12-27 11:48:08 -0800925 }
926
927 private boolean isClientDisablingEnterExitEvent() {
928 final AutofillClient client = getClient();
929 return client != null && client.isDisablingEnterExitEventForAutofill();
930 }
931
Felipe Lemed1146422017-04-26 10:17:05 -0700932 private void notifyViewEntered(@NonNull View view, int flags) {
Svet Ganov43574b02017-04-12 09:25:20 -0700933 if (!hasAutofillFeature()) {
934 return;
935 }
Dake Gu67decfa2017-12-27 11:48:08 -0800936 AutofillCallback callback;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700937 synchronized (mLock) {
Dake Gu67decfa2017-12-27 11:48:08 -0800938 callback = notifyViewEnteredLocked(view, flags);
939 }
Felipe Lemec7b45292017-09-19 09:06:20 -0700940
Dake Gu67decfa2017-12-27 11:48:08 -0800941 if (callback != null) {
942 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
943 }
944 }
Svet Ganov782043c2017-02-11 00:52:02 +0000945
Dake Gu67decfa2017-12-27 11:48:08 -0800946 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
Andreas Gampe3f24e692018-02-05 13:24:28 -0800947 @GuardedBy("mLock")
Dake Gu67decfa2017-12-27 11:48:08 -0800948 private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) {
Felipe Leme42b97932018-02-20 13:04:31 -0800949 final AutofillId id = view.getAutofillId();
Felipe Leme9a15c0f2018-02-14 17:26:46 -0800950 if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
Dake Gu67decfa2017-12-27 11:48:08 -0800951
952 AutofillCallback callback = null;
953
954 ensureServiceClientAddedIfNeededLocked();
955
Felipe Lemedf30f272019-03-19 13:47:48 -0700956 if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
Felipe Lemeadb34f52019-03-15 16:24:26 -0700957 if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
958
Dake Gu67decfa2017-12-27 11:48:08 -0800959 if (mCallback != null) {
960 callback = mCallback;
961 }
962 } else {
963 // don't notify entered when Activity is already in background
964 if (!isClientDisablingEnterExitEvent()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700965 final AutofillValue value = view.getAutofillValue();
966
Felipe Lemec24a56a2017-08-03 14:27:57 -0700967 if (!isActiveLocked()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700968 // Starts new session.
Philip P. Moltmann134cee22017-05-06 11:28:38 -0700969 startSessionLocked(id, null, value, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700970 } else {
971 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -0700972 updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700973 }
Felipe Leme9a15c0f2018-02-14 17:26:46 -0800974 addEnteredIdLocked(id);
Felipe Leme24aae152017-03-15 12:33:01 -0700975 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800976 }
Dake Gu67decfa2017-12-27 11:48:08 -0800977 return callback;
Felipe Lemebab851c2017-02-03 18:45:08 -0800978 }
979
980 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700981 * Called when a {@link View} that supports autofill is exited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800982 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700983 * @param view {@link View} that was exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800984 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700985 public void notifyViewExited(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700986 if (!hasAutofillFeature()) {
987 return;
988 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700989 synchronized (mLock) {
Dake Gu67decfa2017-12-27 11:48:08 -0800990 notifyViewExitedLocked(view);
991 }
992 }
Philip P. Moltmann44611812017-02-23 12:52:46 -0800993
Andreas Gampe3f24e692018-02-05 13:24:28 -0800994 @GuardedBy("mLock")
Dake Gu67decfa2017-12-27 11:48:08 -0800995 void notifyViewExitedLocked(@NonNull View view) {
996 ensureServiceClientAddedIfNeededLocked();
997
Felipe Lemedf30f272019-03-19 13:47:48 -0700998 if ((mEnabled || mEnabledForAugmentedAutofillOnly) && isActiveLocked()) {
Dake Gu67decfa2017-12-27 11:48:08 -0800999 // dont notify exited when Activity is already in background
1000 if (!isClientDisablingEnterExitEvent()) {
Felipe Leme42b97932018-02-20 13:04:31 -08001001 final AutofillId id = view.getAutofillId();
Philip P. Moltmann44611812017-02-23 12:52:46 -08001002
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001003 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -07001004 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001005 }
Philip P. Moltmann44611812017-02-23 12:52:46 -08001006 }
1007 }
1008
1009 /**
Svetoslav Ganov4db89ba2017-06-28 16:50:37 -07001010 * Called when a {@link View view's} visibility changed.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001011 *
1012 * @param view {@link View} that was exited.
1013 * @param isVisible visible if the view is visible in the view hierarchy.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001014 */
Svetoslav Ganov4db89ba2017-06-28 16:50:37 -07001015 public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) {
1016 notifyViewVisibilityChangedInternal(view, 0, isVisible, false);
1017 }
1018
1019 /**
1020 * Called when a virtual view's visibility changed.
1021 *
1022 * @param view {@link View} that was exited.
1023 * @param virtualId id identifying the virtual child inside the parent view.
1024 * @param isVisible visible if the view is visible in the view hierarchy.
1025 */
1026 public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) {
1027 notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true);
1028 }
1029
1030 /**
1031 * Called when a view/virtual view's visibility changed.
1032 *
1033 * @param view {@link View} that was exited.
1034 * @param virtualId id identifying the virtual child inside the parent view.
1035 * @param isVisible visible if the view is visible in the view hierarchy.
1036 * @param virtual Whether the view is virtual.
1037 */
1038 private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId,
1039 boolean isVisible, boolean virtual) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001040 synchronized (mLock) {
Felipe Lemeadb34f52019-03-15 16:24:26 -07001041 if (mForAugmentedAutofillOnly) {
1042 if (sVerbose) {
1043 Log.v(TAG, "notifyViewVisibilityChanged(): ignoring on augmented only mode");
1044 }
1045 return;
1046 }
Felipe Lemec24a56a2017-08-03 14:27:57 -07001047 if (mEnabled && isActiveLocked()) {
Felipe Leme305fd402018-09-11 18:20:42 +00001048 final AutofillId id = virtual ? getAutofillId(view, virtualId)
Svetoslav Ganov4db89ba2017-06-28 16:50:37 -07001049 : view.getAutofillId();
Felipe Leme42b97932018-02-20 13:04:31 -08001050 if (sVerbose) Log.v(TAG, "visibility changed for " + id + ": " + isVisible);
Felipe Leme27e20222017-05-18 15:24:11 -07001051 if (!isVisible && mFillableIds != null) {
Felipe Leme27e20222017-05-18 15:24:11 -07001052 if (mFillableIds.contains(id)) {
1053 if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible");
1054 requestHideFillUi(id, view);
1055 }
1056 }
1057 if (mTrackedViews != null) {
Dake Gu67decfa2017-12-27 11:48:08 -08001058 mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible);
Felipe Leme42b97932018-02-20 13:04:31 -08001059 } else if (sVerbose) {
1060 Log.v(TAG, "Ignoring visibility change on " + id + ": no tracked views");
Felipe Leme27e20222017-05-18 15:24:11 -07001061 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001062 }
1063 }
1064 }
1065
1066 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001067 * Called when a virtual view that supports autofill is entered.
Philip P. Moltmann44611812017-02-23 12:52:46 -08001068 *
Felipe Leme6dcec872017-05-25 11:24:23 -07001069 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
1070 * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas,
1071 * the absolute bounds could be calculated by:
1072 *
1073 * <pre class="prettyprint">
1074 * int offset[] = new int[2];
1075 * getLocationOnScreen(offset);
1076 * Rect absBounds = new Rect(bounds.left + offset[0],
1077 * bounds.top + offset[1],
1078 * bounds.right + offset[0], bounds.bottom + offset[1]);
1079 * </pre>
1080 *
1081 * @param view the virtual view parent.
1082 * @param virtualId id identifying the virtual child inside the parent view.
1083 * @param absBounds absolute boundaries of the virtual view in the screen.
Felipe Lemebab851c2017-02-03 18:45:08 -08001084 */
Felipe Leme6dcec872017-05-25 11:24:23 -07001085 public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
1086 notifyViewEntered(view, virtualId, absBounds, 0);
Felipe Lemed1146422017-04-26 10:17:05 -07001087 }
1088
Felipe Leme6dcec872017-05-25 11:24:23 -07001089 private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) {
Svet Ganov43574b02017-04-12 09:25:20 -07001090 if (!hasAutofillFeature()) {
1091 return;
1092 }
Dake Gu67decfa2017-12-27 11:48:08 -08001093 AutofillCallback callback;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001094 synchronized (mLock) {
Dake Gu67decfa2017-12-27 11:48:08 -08001095 callback = notifyViewEnteredLocked(view, virtualId, bounds, flags);
1096 }
Felipe Leme17292d12017-10-24 14:03:10 -07001097
Dake Gu67decfa2017-12-27 11:48:08 -08001098 if (callback != null) {
1099 callback.onAutofillEvent(view, virtualId,
1100 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1101 }
1102 }
Svet Ganov782043c2017-02-11 00:52:02 +00001103
Dake Gu67decfa2017-12-27 11:48:08 -08001104 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
Andreas Gampe3f24e692018-02-05 13:24:28 -08001105 @GuardedBy("mLock")
Dake Gu67decfa2017-12-27 11:48:08 -08001106 private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds,
1107 int flags) {
Felipe Leme305fd402018-09-11 18:20:42 +00001108 final AutofillId id = getAutofillId(view, virtualId);
Dake Gu67decfa2017-12-27 11:48:08 -08001109 AutofillCallback callback = null;
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001110 if (shouldIgnoreViewEnteredLocked(id, flags)) return callback;
Dake Gu67decfa2017-12-27 11:48:08 -08001111
1112 ensureServiceClientAddedIfNeededLocked();
1113
Felipe Lemedf30f272019-03-19 13:47:48 -07001114 if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
Felipe Lemef3b844b2018-10-02 13:50:50 -07001115 if (sVerbose) {
1116 Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
1117 }
Dake Gu67decfa2017-12-27 11:48:08 -08001118 if (mCallback != null) {
1119 callback = mCallback;
1120 }
1121 } else {
1122 // don't notify entered when Activity is already in background
1123 if (!isClientDisablingEnterExitEvent()) {
Felipe Lemec24a56a2017-08-03 14:27:57 -07001124 if (!isActiveLocked()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001125 // Starts new session.
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001126 startSessionLocked(id, bounds, null, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001127 } else {
1128 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -07001129 updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001130 }
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001131 addEnteredIdLocked(id);
Felipe Leme24aae152017-03-15 12:33:01 -07001132 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001133 }
Dake Gu67decfa2017-12-27 11:48:08 -08001134 return callback;
Philip P. Moltmann44611812017-02-23 12:52:46 -08001135 }
1136
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001137 @GuardedBy("mLock")
1138 private void addEnteredIdLocked(@NonNull AutofillId id) {
1139 if (mEnteredIds == null) {
1140 mEnteredIds = new ArraySet<>(1);
1141 }
1142 mEnteredIds.add(id);
1143 }
1144
Philip P. Moltmann44611812017-02-23 12:52:46 -08001145 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001146 * Called when a virtual view that supports autofill is exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -08001147 *
Felipe Leme6dcec872017-05-25 11:24:23 -07001148 * @param view the virtual view parent.
1149 * @param virtualId id identifying the virtual child inside the parent view.
Philip P. Moltmann44611812017-02-23 12:52:46 -08001150 */
Felipe Leme6dcec872017-05-25 11:24:23 -07001151 public void notifyViewExited(@NonNull View view, int virtualId) {
Felipe Leme54cc6832018-03-06 12:54:31 -08001152 if (sVerbose) Log.v(TAG, "notifyViewExited(" + view.getAutofillId() + ", " + virtualId);
Svet Ganov43574b02017-04-12 09:25:20 -07001153 if (!hasAutofillFeature()) {
1154 return;
1155 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001156 synchronized (mLock) {
Dake Gu67decfa2017-12-27 11:48:08 -08001157 notifyViewExitedLocked(view, virtualId);
1158 }
1159 }
Philip P. Moltmann44611812017-02-23 12:52:46 -08001160
Andreas Gampe3f24e692018-02-05 13:24:28 -08001161 @GuardedBy("mLock")
Dake Gu67decfa2017-12-27 11:48:08 -08001162 private void notifyViewExitedLocked(@NonNull View view, int virtualId) {
1163 ensureServiceClientAddedIfNeededLocked();
1164
Felipe Lemedf30f272019-03-19 13:47:48 -07001165 if ((mEnabled || mEnabledForAugmentedAutofillOnly) && isActiveLocked()) {
Dake Gu67decfa2017-12-27 11:48:08 -08001166 // don't notify exited when Activity is already in background
1167 if (!isClientDisablingEnterExitEvent()) {
Felipe Leme305fd402018-09-11 18:20:42 +00001168 final AutofillId id = getAutofillId(view, virtualId);
Philip P. Moltmann44611812017-02-23 12:52:46 -08001169
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001170 // Update focus on existing session.
Felipe Leme0aa4c502017-04-26 12:36:01 -07001171 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001172 }
Svet Ganov782043c2017-02-11 00:52:02 +00001173 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001174 }
1175
1176 /**
Felipe Leme640f30a2017-03-06 15:44:06 -08001177 * Called to indicate the value of an autofillable {@link View} changed.
Felipe Lemebab851c2017-02-03 18:45:08 -08001178 *
Philip P. Moltmann44611812017-02-23 12:52:46 -08001179 * @param view view whose value changed.
Felipe Lemebab851c2017-02-03 18:45:08 -08001180 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001181 public void notifyValueChanged(View view) {
Svet Ganov43574b02017-04-12 09:25:20 -07001182 if (!hasAutofillFeature()) {
1183 return;
1184 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001185 AutofillId id = null;
1186 boolean valueWasRead = false;
1187 AutofillValue value = null;
1188
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001189 synchronized (mLock) {
Felipe Lemeadb34f52019-03-15 16:24:26 -07001190 if (mForAugmentedAutofillOnly) {
1191 if (sVerbose) Log.v(TAG, "notifyValueChanged(): ignoring on augmented only mode");
1192 return;
1193 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001194 // If the session is gone some fields might still be highlighted, hence we have to
1195 // remove the isAutofilled property even if no sessions are active.
1196 if (mLastAutofilledData == null) {
1197 view.setAutofilled(false);
1198 } else {
Felipe Leme42b97932018-02-20 13:04:31 -08001199 id = view.getAutofillId();
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001200 if (mLastAutofilledData.containsKey(id)) {
1201 value = view.getAutofillValue();
1202 valueWasRead = true;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001203
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001204 if (Objects.equals(mLastAutofilledData.get(id), value)) {
1205 view.setAutofilled(true);
1206 } else {
1207 view.setAutofilled(false);
1208 mLastAutofilledData.remove(id);
1209 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001210 } else {
1211 view.setAutofilled(false);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001212 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001213 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001214
Felipe Lemec24a56a2017-08-03 14:27:57 -07001215 if (!mEnabled || !isActiveLocked()) {
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001216 if (sVerbose) {
1217 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId()
1218 + "): ignoring on state " + getStateAsStringLocked());
Felipe Lemec7b45292017-09-19 09:06:20 -07001219 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001220 return;
1221 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001222
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001223 if (id == null) {
Felipe Leme42b97932018-02-20 13:04:31 -08001224 id = view.getAutofillId();
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001225 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001226
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001227 if (!valueWasRead) {
1228 value = view.getAutofillValue();
1229 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07001230
Felipe Leme0aa4c502017-04-26 12:36:01 -07001231 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001232 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001233 }
1234
Felipe Lemebab851c2017-02-03 18:45:08 -08001235 /**
Felipe Leme6dcec872017-05-25 11:24:23 -07001236 * Called to indicate the value of an autofillable virtual view has changed.
Felipe Lemebab851c2017-02-03 18:45:08 -08001237 *
Felipe Leme6dcec872017-05-25 11:24:23 -07001238 * @param view the virtual view parent.
1239 * @param virtualId id identifying the virtual child inside the parent view.
Felipe Lemebab851c2017-02-03 18:45:08 -08001240 * @param value new value of the child.
1241 */
Felipe Leme6dcec872017-05-25 11:24:23 -07001242 public void notifyValueChanged(View view, int virtualId, AutofillValue value) {
Svet Ganov43574b02017-04-12 09:25:20 -07001243 if (!hasAutofillFeature()) {
1244 return;
1245 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001246 synchronized (mLock) {
Felipe Lemeadb34f52019-03-15 16:24:26 -07001247 if (mForAugmentedAutofillOnly) {
1248 if (sVerbose) Log.v(TAG, "notifyValueChanged(): ignoring on augmented only mode");
1249 return;
1250 }
Felipe Lemec24a56a2017-08-03 14:27:57 -07001251 if (!mEnabled || !isActiveLocked()) {
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001252 if (sVerbose) {
1253 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId
1254 + "): ignoring on state " + getStateAsStringLocked());
1255 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001256 return;
1257 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001258
Felipe Leme305fd402018-09-11 18:20:42 +00001259 final AutofillId id = getAutofillId(view, virtualId);
Felipe Leme0aa4c502017-04-26 12:36:01 -07001260 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001261 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001262 }
1263
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001264 /**
Felipe Leme67e62092018-02-15 14:47:31 -08001265 * Called to indicate a {@link View} is clicked.
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001266 *
Felipe Leme67e62092018-02-15 14:47:31 -08001267 * @param view view that has been clicked.
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001268 */
Felipe Leme67e62092018-02-15 14:47:31 -08001269 public void notifyViewClicked(@NonNull View view) {
1270 notifyViewClicked(view.getAutofillId());
1271 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001272
Felipe Leme67e62092018-02-15 14:47:31 -08001273 /**
1274 * Called to indicate a virtual view has been clicked.
1275 *
1276 * @param view the virtual view parent.
1277 * @param virtualId id identifying the virtual child inside the parent view.
1278 */
1279 public void notifyViewClicked(@NonNull View view, int virtualId) {
Felipe Leme305fd402018-09-11 18:20:42 +00001280 notifyViewClicked(getAutofillId(view, virtualId));
Felipe Leme67e62092018-02-15 14:47:31 -08001281 }
1282
1283 private void notifyViewClicked(AutofillId id) {
1284 if (!hasAutofillFeature()) {
1285 return;
1286 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001287 if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
1288
1289 synchronized (mLock) {
Felipe Leme67e62092018-02-15 14:47:31 -08001290 if (!mEnabled || !isActiveLocked()) {
1291 return;
1292 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001293 if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
1294 if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
1295 commitLocked();
Felipe Leme11166522018-05-07 10:18:47 -07001296 mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED));
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001297 }
1298 }
1299 }
1300
1301 /**
1302 * Called by {@link android.app.Activity} to commit or cancel the session on finish.
1303 *
1304 * @hide
1305 */
Felipe Leme5b32ebe2018-02-15 12:52:19 -08001306 public void onActivityFinishing() {
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001307 if (!hasAutofillFeature()) {
1308 return;
1309 }
1310 synchronized (mLock) {
1311 if (mSaveOnFinish) {
Felipe Leme5b32ebe2018-02-15 12:52:19 -08001312 if (sDebug) Log.d(TAG, "onActivityFinishing(): calling commitLocked()");
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001313 commitLocked();
1314 } else {
Felipe Leme5b32ebe2018-02-15 12:52:19 -08001315 if (sDebug) Log.d(TAG, "onActivityFinishing(): calling cancelLocked()");
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001316 cancelLocked();
1317 }
1318 }
1319 }
1320
Felipe Lemebab851c2017-02-03 18:45:08 -08001321 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001322 * Called to indicate the current autofill context should be commited.
Felipe Lemebab851c2017-02-03 18:45:08 -08001323 *
Felipe Lemeafdfe762017-07-24 11:25:56 -07001324 * <p>This method is typically called by {@link View Views} that manage virtual views; for
1325 * example, when the view is rendering an {@code HTML} page with a form and virtual views
1326 * that represent the HTML elements, it should call this method after the form is submitted and
1327 * another page is rendered.
1328 *
1329 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
1330 * methods such as {@link android.app.Activity#finish()}.
Felipe Lemebab851c2017-02-03 18:45:08 -08001331 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001332 public void commit() {
Svet Ganov43574b02017-04-12 09:25:20 -07001333 if (!hasAutofillFeature()) {
1334 return;
1335 }
Felipe Leme5b32ebe2018-02-15 12:52:19 -08001336 if (sVerbose) Log.v(TAG, "commit() called by app");
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001337 synchronized (mLock) {
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001338 commitLocked();
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001339 }
Felipe Leme0200d9e2017-01-24 15:10:26 -08001340 }
1341
Andreas Gampe3f24e692018-02-05 13:24:28 -08001342 @GuardedBy("mLock")
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001343 private void commitLocked() {
1344 if (!mEnabled && !isActiveLocked()) {
1345 return;
1346 }
1347 finishSessionLocked();
1348 }
1349
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001350 /**
1351 * Called to indicate the current autofill context should be cancelled.
1352 *
Felipe Lemeafdfe762017-07-24 11:25:56 -07001353 * <p>This method is typically called by {@link View Views} that manage virtual views; for
1354 * example, when the view is rendering an {@code HTML} page with a form and virtual views
1355 * that represent the HTML elements, it should call this method if the user does not post the
1356 * form but moves to another form in this page.
1357 *
1358 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
1359 * methods such as {@link android.app.Activity#finish()}.
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001360 */
1361 public void cancel() {
Felipe Leme601d2202017-11-17 08:21:23 -08001362 if (sVerbose) Log.v(TAG, "cancel() called by app");
Svet Ganov43574b02017-04-12 09:25:20 -07001363 if (!hasAutofillFeature()) {
1364 return;
1365 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001366 synchronized (mLock) {
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001367 cancelLocked();
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001368 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001369 }
1370
Andreas Gampe3f24e692018-02-05 13:24:28 -08001371 @GuardedBy("mLock")
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001372 private void cancelLocked() {
1373 if (!mEnabled && !isActiveLocked()) {
1374 return;
1375 }
1376 cancelSessionLocked();
1377 }
1378
Svet Ganovf965b872017-04-28 16:34:02 -07001379 /** @hide */
1380 public void disableOwnedAutofillServices() {
1381 disableAutofillServices();
1382 }
1383
Svet Ganovf20a0372017-04-10 17:08:05 -07001384 /**
1385 * If the app calling this API has enabled autofill services they
1386 * will be disabled.
1387 */
Svet Ganovf965b872017-04-28 16:34:02 -07001388 public void disableAutofillServices() {
Svet Ganov43574b02017-04-12 09:25:20 -07001389 if (!hasAutofillFeature()) {
1390 return;
1391 }
Svet Ganovf20a0372017-04-10 17:08:05 -07001392 try {
1393 mService.disableOwnedAutofillServices(mContext.getUserId());
1394 } catch (RemoteException e) {
1395 throw e.rethrowFromSystemServer();
1396 }
1397 }
1398
Felipe Lemedb041182017-04-21 17:33:38 -07001399 /**
1400 * Returns {@code true} if the calling application provides a {@link AutofillService} that is
1401 * enabled for the current user, or {@code false} otherwise.
1402 */
1403 public boolean hasEnabledAutofillServices() {
1404 if (mService == null) return false;
1405
Felipe Lemec0c15a32019-01-08 10:53:33 -08001406 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemedb041182017-04-21 17:33:38 -07001407 try {
Felipe Lemed4e52282018-06-18 13:56:38 -07001408 mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver);
1409 return receiver.getIntResult() == 1;
Felipe Lemedb041182017-04-21 17:33:38 -07001410 } catch (RemoteException e) {
1411 throw e.rethrowFromSystemServer();
1412 }
1413 }
1414
1415 /**
Felipe Leme23c75ff2017-12-14 13:27:44 -08001416 * Returns the component name of the {@link AutofillService} that is enabled for the current
1417 * user.
1418 */
1419 @Nullable
1420 public ComponentName getAutofillServiceComponentName() {
1421 if (mService == null) return null;
1422
Felipe Lemec0c15a32019-01-08 10:53:33 -08001423 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Leme23c75ff2017-12-14 13:27:44 -08001424 try {
Felipe Lemed4e52282018-06-18 13:56:38 -07001425 mService.getAutofillServiceComponentName(receiver);
Felipe Lemec0c15a32019-01-08 10:53:33 -08001426 return receiver.getParcelableResult();
Felipe Leme23c75ff2017-12-14 13:27:44 -08001427 } catch (RemoteException e) {
1428 throw e.rethrowFromSystemServer();
1429 }
1430 }
1431
1432 /**
Felipe Lemef0baef742018-01-26 14:39:39 -08001433 * Gets the id of the {@link UserData} used for
1434 * <a href="AutofillService.html#FieldClassification">field classification</a>.
1435 *
1436 * <p>This method is useful when the service must check the status of the {@link UserData} in
1437 * the device without fetching the whole object.
1438 *
1439 * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
1440 * and it's ignored if the caller currently doesn't have an enabled autofill service for
1441 * the user.
1442 *
1443 * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)}
1444 * or {@code null} if it was reset or if the caller currently does not have an enabled autofill
1445 * service for the user.
1446 */
1447 @Nullable public String getUserDataId() {
1448 try {
Felipe Lemec0c15a32019-01-08 10:53:33 -08001449 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemed4e52282018-06-18 13:56:38 -07001450 mService.getUserDataId(receiver);
Felipe Lemec0c15a32019-01-08 10:53:33 -08001451 return receiver.getStringResult();
Felipe Lemef0baef742018-01-26 14:39:39 -08001452 } catch (RemoteException e) {
1453 e.rethrowFromSystemServer();
1454 return null;
1455 }
1456 }
1457
1458 /**
Felipe Leme78172e72017-12-08 17:01:15 -08001459 * Gets the user data used for
1460 * <a href="AutofillService.html#FieldClassification">field classification</a>.
Felipe Leme452886a2017-11-27 13:09:13 -08001461 *
Felipe Leme27f45732017-12-22 09:05:22 -08001462 * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
1463 * and it's ignored if the caller currently doesn't have an enabled autofill service for
1464 * the user.
Felipe Leme452886a2017-11-27 13:09:13 -08001465 *
Felipe Leme452886a2017-11-27 13:09:13 -08001466 * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
1467 * reset or if the caller currently does not have an enabled autofill service for the user.
Felipe Leme452886a2017-11-27 13:09:13 -08001468 */
Felipe Leme452886a2017-11-27 13:09:13 -08001469 @Nullable public UserData getUserData() {
1470 try {
Felipe Lemec0c15a32019-01-08 10:53:33 -08001471 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemed4e52282018-06-18 13:56:38 -07001472 mService.getUserData(receiver);
Felipe Lemec0c15a32019-01-08 10:53:33 -08001473 return receiver.getParcelableResult();
Felipe Leme452886a2017-11-27 13:09:13 -08001474 } catch (RemoteException e) {
1475 e.rethrowFromSystemServer();
1476 return null;
1477 }
1478 }
1479
1480 /**
Felipe Lemef0baef742018-01-26 14:39:39 -08001481 * Sets the {@link UserData} used for
Felipe Leme78172e72017-12-08 17:01:15 -08001482 * <a href="AutofillService.html#FieldClassification">field classification</a>
Felipe Leme452886a2017-11-27 13:09:13 -08001483 *
1484 * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
1485 * and it's ignored if the caller currently doesn't have an enabled autofill service for
1486 * the user.
Felipe Leme452886a2017-11-27 13:09:13 -08001487 */
Felipe Leme452886a2017-11-27 13:09:13 -08001488 public void setUserData(@Nullable UserData userData) {
1489 try {
1490 mService.setUserData(userData);
1491 } catch (RemoteException e) {
1492 e.rethrowFromSystemServer();
1493 }
1494 }
1495
1496 /**
Felipe Leme78172e72017-12-08 17:01:15 -08001497 * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is
1498 * enabled.
1499 *
1500 * <p>As field classification is an expensive operation, it could be disabled, either
1501 * temporarily (for example, because the service exceeded a rate-limit threshold) or
1502 * permanently (for example, because the device is a low-level device).
1503 *
1504 * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
1505 * and it's ignored if the caller currently doesn't have an enabled autofill service for
1506 * the user.
Felipe Leme329d0402017-12-06 09:22:43 -08001507 */
Felipe Leme329d0402017-12-06 09:22:43 -08001508 public boolean isFieldClassificationEnabled() {
Felipe Lemec0c15a32019-01-08 10:53:33 -08001509 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Leme329d0402017-12-06 09:22:43 -08001510 try {
Felipe Lemed4e52282018-06-18 13:56:38 -07001511 mService.isFieldClassificationEnabled(receiver);
1512 return receiver.getIntResult() == 1;
Felipe Leme329d0402017-12-06 09:22:43 -08001513 } catch (RemoteException e) {
1514 e.rethrowFromSystemServer();
1515 return false;
1516 }
1517 }
1518
1519 /**
Felipe Leme27f45732017-12-22 09:05:22 -08001520 * Gets the name of the default algorithm used for
1521 * <a href="AutofillService.html#FieldClassification">field classification</a>.
1522 *
1523 * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not
1524 * set.
1525 *
1526 * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
1527 * and it's ignored if the caller currently doesn't have an enabled autofill service for
1528 * the user.
1529 */
1530 @Nullable
1531 public String getDefaultFieldClassificationAlgorithm() {
Felipe Lemec0c15a32019-01-08 10:53:33 -08001532 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Leme27f45732017-12-22 09:05:22 -08001533 try {
Felipe Lemed4e52282018-06-18 13:56:38 -07001534 mService.getDefaultFieldClassificationAlgorithm(receiver);
Felipe Lemec0c15a32019-01-08 10:53:33 -08001535 return receiver.getStringResult();
Felipe Leme27f45732017-12-22 09:05:22 -08001536 } catch (RemoteException e) {
1537 e.rethrowFromSystemServer();
1538 return null;
1539 }
1540 }
1541
1542 /**
1543 * Gets the name of all algorithms currently available for
1544 * <a href="AutofillService.html#FieldClassification">field classification</a>.
1545 *
1546 * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
Felipe Lemebc055b02018-01-05 17:04:10 -08001547 * and it returns an empty list if the caller currently doesn't have an enabled autofill service
1548 * for the user.
Felipe Leme27f45732017-12-22 09:05:22 -08001549 */
1550 @NonNull
1551 public List<String> getAvailableFieldClassificationAlgorithms() {
Felipe Lemec0c15a32019-01-08 10:53:33 -08001552 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Leme27f45732017-12-22 09:05:22 -08001553 try {
Felipe Lemed4e52282018-06-18 13:56:38 -07001554 mService.getAvailableFieldClassificationAlgorithms(receiver);
Felipe Lemec0c15a32019-01-08 10:53:33 -08001555 final String[] algorithms = receiver.getStringArrayResult();
Felipe Lemee4ac7402018-01-16 19:37:00 -08001556 return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
Felipe Leme27f45732017-12-22 09:05:22 -08001557 } catch (RemoteException e) {
1558 e.rethrowFromSystemServer();
1559 return null;
1560 }
1561 }
1562
1563 /**
Ricardo Loo33d226c2017-06-27 14:17:33 -07001564 * Returns {@code true} if autofill is supported by the current device and
1565 * is supported for this user.
Felipe Lemedb041182017-04-21 17:33:38 -07001566 *
1567 * <p>Autofill is typically supported, but it could be unsupported in cases like:
1568 * <ol>
1569 * <li>Low-end devices.
1570 * <li>Device policy rules that forbid its usage.
1571 * </ol>
1572 */
1573 public boolean isAutofillSupported() {
1574 if (mService == null) return false;
1575
Felipe Lemec0c15a32019-01-08 10:53:33 -08001576 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemedb041182017-04-21 17:33:38 -07001577 try {
Felipe Lemed4e52282018-06-18 13:56:38 -07001578 mService.isServiceSupported(mContext.getUserId(), receiver);
1579 return receiver.getIntResult() == 1;
Felipe Lemedb041182017-04-21 17:33:38 -07001580 } catch (RemoteException e) {
1581 throw e.rethrowFromSystemServer();
1582 }
1583 }
1584
Felipe Leme637e05e2017-12-06 12:09:37 -08001585 // Note: don't need to use locked suffix because mContext is final.
1586 private AutofillClient getClient() {
Felipe Leme686128e2017-10-17 14:02:20 -07001587 final AutofillClient client = mContext.getAutofillClient();
Felipe Leme261e75f2018-12-17 10:15:08 -08001588 if (client == null && sVerbose) {
1589 Log.v(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
Felipe Leme686128e2017-10-17 14:02:20 -07001590 + mContext);
1591 }
1592 return client;
Svet Ganov782043c2017-02-11 00:52:02 +00001593 }
1594
Dake Gud9dbd272018-03-13 11:38:42 -07001595 /**
1596 * Check if autofill ui is showing, must be called on UI thread.
1597 * @hide
1598 */
1599 public boolean isAutofillUiShowing() {
1600 final AutofillClient client = mContext.getAutofillClient();
Felipe Lemecb2e83d2018-03-19 11:15:00 -07001601 return client != null && client.autofillClientIsFillUiShowing();
Dake Gud9dbd272018-03-13 11:38:42 -07001602 }
1603
Svet Ganov782043c2017-02-11 00:52:02 +00001604 /** @hide */
Dake Gu67decfa2017-12-27 11:48:08 -08001605 public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
Svet Ganov43574b02017-04-12 09:25:20 -07001606 if (!hasAutofillFeature()) {
1607 return;
1608 }
Felipe Leme85d1c2d2017-04-21 08:56:04 -07001609 // TODO: the result code is being ignored, so this method is not reliably
Felipe Lemed633f072017-02-14 10:17:17 -08001610 // handling the cases where it's not RESULT_OK: it works fine if the service does not
1611 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
1612 // service set the extra and returned RESULT_CANCELED...
1613
Felipe Leme1ad95f02018-06-15 13:07:34 -07001614 if (sDebug) {
1615 Log.d(TAG, "onAuthenticationResult(): id= " + authenticationId + ", data=" + data);
1616 }
Felipe Lemed633f072017-02-14 10:17:17 -08001617
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001618 synchronized (mLock) {
Dake Gu67decfa2017-12-27 11:48:08 -08001619 if (!isActiveLocked()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001620 return;
1621 }
Dake Gu67decfa2017-12-27 11:48:08 -08001622 // If authenticate activity closes itself during onCreate(), there is no onStop/onStart
1623 // of app activity. We enforce enter event to re-show fill ui in such case.
1624 // CTS example:
1625 // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt
1626 // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt
1627 if (!mOnInvisibleCalled && focusView != null
1628 && focusView.canNotifyAutofillEnterExitEvent()) {
1629 notifyViewExitedLocked(focusView);
1630 notifyViewEnteredLocked(focusView, 0);
1631 }
1632 if (data == null) {
1633 // data is set to null when result is not RESULT_OK
1634 return;
1635 }
1636
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001637 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
1638 final Bundle responseData = new Bundle();
1639 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
Felipe Lemea9372382017-10-09 14:42:36 -07001640 final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
1641 if (newClientState != null) {
1642 responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
1643 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001644 try {
Svetoslav Ganova9379d02017-05-09 17:40:24 -07001645 mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
1646 mContext.getUserId());
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001647 } catch (RemoteException e) {
1648 Log.e(TAG, "Error delivering authentication result", e);
1649 }
Svet Ganov782043c2017-02-11 00:52:02 +00001650 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001651 }
1652
Felipe Leme42b97932018-02-20 13:04:31 -08001653 /**
1654 * Gets the next unique autofill ID for the activity context.
1655 *
1656 * <p>Typically used to manage views whose content is recycled - see
1657 * {@link View#setAutofillId(AutofillId)} for more info.
1658 *
1659 * @return An ID that is unique in the activity, or {@code null} if autofill is not supported in
1660 * the {@link Context} associated with this {@link AutofillManager}.
1661 */
1662 @Nullable
1663 public AutofillId getNextAutofillId() {
1664 final AutofillClient client = getClient();
1665 if (client == null) return null;
1666
1667 final AutofillId id = client.autofillClientGetNextAutofillId();
1668
1669 if (id == null && sDebug) {
1670 Log.d(TAG, "getNextAutofillId(): client " + client + " returned null");
1671 }
1672
1673 return id;
Felipe Leme0200d9e2017-01-24 15:10:26 -08001674 }
1675
Felipe Leme305fd402018-09-11 18:20:42 +00001676 private static AutofillId getAutofillId(View parent, int virtualId) {
1677 return new AutofillId(parent.getAutofillViewId(), virtualId);
Felipe Lemebab851c2017-02-03 18:45:08 -08001678 }
1679
Andreas Gampe3f24e692018-02-05 13:24:28 -08001680 @GuardedBy("mLock")
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001681 private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
1682 @NonNull AutofillValue value, int flags) {
Felipe Lemea7de4022019-02-19 17:16:45 -08001683 if (mEnteredForAugmentedAutofillIds != null
Felipe Lemedf30f272019-03-19 13:47:48 -07001684 && mEnteredForAugmentedAutofillIds.contains(id)
1685 || mEnabledForAugmentedAutofillOnly) {
Felipe Lemea7de4022019-02-19 17:16:45 -08001686 if (sVerbose) Log.v(TAG, "Starting session for augmented autofill on " + id);
Felipe Lemedf30f272019-03-19 13:47:48 -07001687 flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
Felipe Lemea7de4022019-02-19 17:16:45 -08001688 }
Felipe Leme9f9ee252017-04-27 13:56:22 -07001689 if (sVerbose) {
1690 Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
felipealfd3f8f62018-02-07 11:49:07 +01001691 + ", flags=" + flags + ", state=" + getStateAsStringLocked()
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001692 + ", compatMode=" + isCompatibilityModeEnabledLocked()
Felipe Lemedf30f272019-03-19 13:47:48 -07001693 + ", augmentedOnly=" + mForAugmentedAutofillOnly
1694 + ", enabledAugmentedOnly=" + mEnabledForAugmentedAutofillOnly
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001695 + ", enteredIds=" + mEnteredIds);
Felipe Lemebab851c2017-02-03 18:45:08 -08001696 }
Felipe Leme3103c632017-12-18 15:05:14 -08001697 if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
Felipe Lemec7b45292017-09-19 09:06:20 -07001698 if (sVerbose) {
1699 Log.v(TAG, "not automatically starting session for " + id
Felipe Leme3103c632017-12-18 15:05:14 -08001700 + " on state " + getStateAsStringLocked() + " and flags " + flags);
Felipe Lemec7b45292017-09-19 09:06:20 -07001701 }
Felipe Lemec24a56a2017-08-03 14:27:57 -07001702 return;
1703 }
Felipe Lemebab851c2017-02-03 18:45:08 -08001704 try {
Felipe Leme637e05e2017-12-06 12:09:37 -08001705 final AutofillClient client = getClient();
felipealfd3f8f62018-02-07 11:49:07 +01001706 if (client == null) return; // NOTE: getClient() already logged it..
Felipe Leme637e05e2017-12-06 12:09:37 -08001707
Felipe Lemec0c15a32019-01-08 10:53:33 -08001708 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemeadb34f52019-03-15 16:24:26 -07001709 final ComponentName componentName = client.autofillClientGetComponentName();
Felipe Lemed4e52282018-06-18 13:56:38 -07001710 mService.startSession(client.autofillClientGetActivityToken(),
Felipe Lemee6010f22017-03-03 11:19:51 -08001711 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
Felipe Lemeadb34f52019-03-15 16:24:26 -07001712 mCallback != null, flags, componentName,
Felipe Lemed4e52282018-06-18 13:56:38 -07001713 isCompatibilityModeEnabledLocked(), receiver);
1714 mSessionId = receiver.getIntResult();
Felipe Lemec24a56a2017-08-03 14:27:57 -07001715 if (mSessionId != NO_SESSION) {
1716 mState = STATE_ACTIVE;
1717 }
Felipe Lemeadb34f52019-03-15 16:24:26 -07001718 final int extraFlags = receiver.getOptionalExtraIntResult(0);
1719 if ((extraFlags & FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
1720 if (sDebug) Log.d(TAG, "startSession(" + componentName + "): for augmented only");
1721 mForAugmentedAutofillOnly = true;
1722 }
Svetoslav Ganov24c90452017-12-27 15:17:14 -08001723 client.autofillClientResetableStateAvailable();
Svet Ganov782043c2017-02-11 00:52:02 +00001724 } catch (RemoteException e) {
1725 throw e.rethrowFromSystemServer();
1726 }
1727 }
1728
Andreas Gampe3f24e692018-02-05 13:24:28 -08001729 @GuardedBy("mLock")
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001730 private void finishSessionLocked() {
Felipe Lemec7b45292017-09-19 09:06:20 -07001731 if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
Felipe Lemec24a56a2017-08-03 14:27:57 -07001732
1733 if (!isActiveLocked()) return;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001734
Svet Ganov782043c2017-02-11 00:52:02 +00001735 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001736 mService.finishSession(mSessionId, mContext.getUserId());
Felipe Lemebab851c2017-02-03 18:45:08 -08001737 } catch (RemoteException e) {
1738 throw e.rethrowFromSystemServer();
1739 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001740
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001741 resetSessionLocked(/* resetEnteredIds= */ true);
Felipe Lemebab851c2017-02-03 18:45:08 -08001742 }
1743
Andreas Gampe3f24e692018-02-05 13:24:28 -08001744 @GuardedBy("mLock")
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001745 private void cancelSessionLocked() {
Felipe Lemec7b45292017-09-19 09:06:20 -07001746 if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
Felipe Lemec24a56a2017-08-03 14:27:57 -07001747
1748 if (!isActiveLocked()) return;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001749
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001750 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001751 mService.cancelSession(mSessionId, mContext.getUserId());
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001752 } catch (RemoteException e) {
1753 throw e.rethrowFromSystemServer();
1754 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001755
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001756 resetSessionLocked(/* resetEnteredIds= */ true);
Svet Ganov48f10a22017-04-26 18:49:30 -07001757 }
1758
Andreas Gampe3f24e692018-02-05 13:24:28 -08001759 @GuardedBy("mLock")
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001760 private void resetSessionLocked(boolean resetEnteredIds) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001761 mSessionId = NO_SESSION;
Felipe Lemec24a56a2017-08-03 14:27:57 -07001762 mState = STATE_UNKNOWN;
Svet Ganov48f10a22017-04-26 18:49:30 -07001763 mTrackedViews = null;
Felipe Lemec24a56a2017-08-03 14:27:57 -07001764 mFillableIds = null;
Felipe Leme2fe3ade2017-09-28 15:03:36 -07001765 mSaveTriggerId = null;
Dake Gub0fa3782018-02-26 12:25:14 -08001766 mIdShownFillUi = null;
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001767 if (resetEnteredIds) {
1768 mEnteredIds = null;
1769 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -07001770 }
1771
Andreas Gampe3f24e692018-02-05 13:24:28 -08001772 @GuardedBy("mLock")
Felipe Leme0aa4c502017-04-26 12:36:01 -07001773 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
1774 int flags) {
Felipe Leme9a15c0f2018-02-14 17:26:46 -08001775 if (sVerbose) {
Felipe Leme9f9ee252017-04-27 13:56:22 -07001776 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
1777 + ", value=" + value + ", action=" + action + ", flags=" + flags);
Felipe Lemebab851c2017-02-03 18:45:08 -08001778 }
Felipe Leme3461d3c2017-01-19 08:54:55 -08001779 try {
Felipe Lemed62b5162018-06-06 15:08:25 -07001780 mService.updateSession(mSessionId, id, bounds, value, action, flags,
1781 mContext.getUserId());
Felipe Leme3461d3c2017-01-19 08:54:55 -08001782 } catch (RemoteException e) {
1783 throw e.rethrowFromSystemServer();
1784 }
1785 }
Svet Ganov782043c2017-02-11 00:52:02 +00001786
Andreas Gampe3f24e692018-02-05 13:24:28 -08001787 @GuardedBy("mLock")
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001788 private void ensureServiceClientAddedIfNeededLocked() {
Felipe Lemedf30f272019-03-19 13:47:48 -07001789 final AutofillClient client = getClient();
1790 if (client == null) {
Svet Ganov782043c2017-02-11 00:52:02 +00001791 return;
1792 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001793
Svet Ganov782043c2017-02-11 00:52:02 +00001794 if (mServiceClient == null) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001795 mServiceClient = new AutofillManagerClient(this);
Svet Ganov782043c2017-02-11 00:52:02 +00001796 try {
Koji Fukuiccec6a62017-10-18 17:48:40 +09001797 final int userId = mContext.getUserId();
Felipe Lemec0c15a32019-01-08 10:53:33 -08001798 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemedf30f272019-03-19 13:47:48 -07001799 mService.addClient(mServiceClient, client.autofillClientGetComponentName(),
1800 userId, receiver);
Felipe Lemed4e52282018-06-18 13:56:38 -07001801 final int flags = receiver.getIntResult();
Felipe Leme9f9ee252017-04-27 13:56:22 -07001802 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
1803 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
1804 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
Felipe Lemedf30f272019-03-19 13:47:48 -07001805 mEnabledForAugmentedAutofillOnly = (flags
1806 & FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY) != 0;
1807 if (sVerbose) {
1808 Log.v(TAG, "receiver results: flags=" + flags + " enabled=" + mEnabled
1809 + ", enabledForAugmentedOnly: " + mEnabledForAugmentedAutofillOnly);
1810 }
Koji Fukuiccec6a62017-10-18 17:48:40 +09001811 final IAutoFillManager service = mService;
1812 final IAutoFillManagerClient serviceClient = mServiceClient;
1813 mServiceClientCleaner = Cleaner.create(this, () -> {
Felipe Leme559e21d2019-01-18 17:57:21 -08001814 // TODO(b/123100811): call service to also remove reference to
Felipe Leme284ad1c2018-11-15 18:16:12 -08001815 // mAugmentedAutofillServiceClient
Koji Fukuiccec6a62017-10-18 17:48:40 +09001816 try {
1817 service.removeClient(serviceClient, userId);
1818 } catch (RemoteException e) {
1819 }
1820 });
Svet Ganov782043c2017-02-11 00:52:02 +00001821 } catch (RemoteException e) {
1822 throw e.rethrowFromSystemServer();
1823 }
1824 }
1825 }
1826
Felipe Lemee6010f22017-03-03 11:19:51 -08001827 /**
1828 * Registers a {@link AutofillCallback} to receive autofill events.
1829 *
1830 * @param callback callback to receive events.
1831 */
1832 public void registerCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -07001833 if (!hasAutofillFeature()) {
1834 return;
1835 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001836 synchronized (mLock) {
1837 if (callback == null) return;
Felipe Lemee6010f22017-03-03 11:19:51 -08001838
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001839 final boolean hadCallback = mCallback != null;
1840 mCallback = callback;
Felipe Lemee6010f22017-03-03 11:19:51 -08001841
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001842 if (!hadCallback) {
1843 try {
1844 mService.setHasCallback(mSessionId, mContext.getUserId(), true);
1845 } catch (RemoteException e) {
1846 throw e.rethrowFromSystemServer();
1847 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001848 }
1849 }
1850 }
1851
1852 /**
1853 * Unregisters a {@link AutofillCallback} to receive autofill events.
1854 *
1855 * @param callback callback to stop receiving events.
1856 */
1857 public void unregisterCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -07001858 if (!hasAutofillFeature()) {
1859 return;
1860 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001861 synchronized (mLock) {
1862 if (callback == null || mCallback == null || callback != mCallback) return;
Felipe Lemee6010f22017-03-03 11:19:51 -08001863
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001864 mCallback = null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001865
Felipe Lemee6010f22017-03-03 11:19:51 -08001866 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001867 mService.setHasCallback(mSessionId, mContext.getUserId(), false);
Felipe Lemee6010f22017-03-03 11:19:51 -08001868 } catch (RemoteException e) {
1869 throw e.rethrowFromSystemServer();
1870 }
1871 }
1872 }
1873
Felipe Leme559e21d2019-01-18 17:57:21 -08001874 /**
Felipe Leme559e21d2019-01-18 17:57:21 -08001875 * Explicitly limits augmented autofill to the given packages and activities.
1876 *
Felipe Leme559e21d2019-01-18 17:57:21 -08001877 * <p>To reset the whitelist, call it passing {@code null} to both arguments.
1878 *
1879 * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like
1880 * apps that uses addresses. For example, if the service wants to support augmented autofill on
1881 * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2}
1882 * of {@code AddressApp2}, it would call:
1883 * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"),
1884 * Arrays.asList(new ComponentName("AddressApp2", "act1"),
1885 * new ComponentName("AddressApp2", "act2")));}
1886 *
1887 * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
1888 * service, and it's ignored if the caller isn't it.
1889 *
1890 * @hide
1891 */
1892 @SystemApi
1893 @TestApi
Felipe Leme7a3c9f52019-02-13 16:32:49 -08001894 public void setAugmentedAutofillWhitelist(@Nullable Set<String> packages,
1895 @Nullable Set<ComponentName> activities) {
Felipe Leme80e7bf12019-02-14 10:56:52 -08001896 if (!hasAutofillFeature()) {
1897 return;
1898 }
1899
1900 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
1901 final int resultCode;
1902 try {
1903 mService.setAugmentedAutofillWhitelist(toList(packages), toList(activities),
1904 resultReceiver);
1905 resultCode = resultReceiver.getIntResult();
1906 } catch (RemoteException e) {
1907 throw e.rethrowFromSystemServer();
1908 }
1909 switch (resultCode) {
1910 case RESULT_OK:
1911 return;
1912 case RESULT_CODE_NOT_SERVICE:
1913 throw new SecurityException("caller is not user's Augmented Autofill Service");
1914 default:
1915 Log.wtf(TAG, "setAugmentedAutofillWhitelist(): received invalid result: "
1916 + resultCode);
1917 }
1918 }
1919
Felipe Lemea7de4022019-02-19 17:16:45 -08001920 /**
1921 * Notifies that a non-autofillable view was entered because the activity is whitelisted for
1922 * augmented autofill.
1923 *
1924 * <p>This method is necessary to set the right flag on start, so the server-side session
1925 * doesn't trigger the standard autofill workflow, but the augmented's instead.
1926 *
1927 * @hide
1928 */
1929 public void notifyViewEnteredForAugmentedAutofill(@NonNull View view) {
1930 final AutofillId id = view.getAutofillId();
1931 synchronized (mLock) {
1932 if (mEnteredForAugmentedAutofillIds == null) {
1933 mEnteredForAugmentedAutofillIds = new ArraySet<>(1);
1934 }
1935 mEnteredForAugmentedAutofillIds.add(id);
1936 }
1937 }
1938
Philip P. Moltmann134cee22017-05-06 11:28:38 -07001939 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
1940 Rect anchorBounds, IAutofillWindowPresenter presenter) {
1941 final View anchor = findView(id);
Felipe Leme4753bb02017-03-22 20:24:00 -07001942 if (anchor == null) {
1943 return;
1944 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001945
1946 AutofillCallback callback = null;
1947 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001948 if (mSessionId == sessionId) {
Felipe Leme637e05e2017-12-06 12:09:37 -08001949 AutofillClient client = getClient();
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001950
1951 if (client != null) {
Svetoslav Ganov24c90452017-12-27 15:17:14 -08001952 if (client.autofillClientRequestShowFillUi(anchor, width, height,
Dake Gub0fa3782018-02-26 12:25:14 -08001953 anchorBounds, presenter)) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001954 callback = mCallback;
Dake Gub0fa3782018-02-26 12:25:14 -08001955 mIdShownFillUi = id;
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001956 }
1957 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001958 }
1959 }
1960
1961 if (callback != null) {
Felipe Lemea75333c2019-01-22 15:45:32 -08001962 if (id.isVirtualInt()) {
1963 callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001964 AutofillCallback.EVENT_INPUT_SHOWN);
1965 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001966 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
Felipe Leme4753bb02017-03-22 20:24:00 -07001967 }
1968 }
1969 }
1970
Svetoslav Ganova9379d02017-05-09 17:40:24 -07001971 private void authenticate(int sessionId, int authenticationId, IntentSender intent,
1972 Intent fillInIntent) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001973 synchronized (mLock) {
1974 if (sessionId == mSessionId) {
Felipe Leme637e05e2017-12-06 12:09:37 -08001975 final AutofillClient client = getClient();
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001976 if (client != null) {
Dake Gu67decfa2017-12-27 11:48:08 -08001977 // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill()
1978 // before onAuthenticationResult()
1979 mOnInvisibleCalled = false;
Svetoslav Ganov24c90452017-12-27 15:17:14 -08001980 client.autofillClientAuthenticate(authenticationId, intent, fillInIntent);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001981 }
1982 }
1983 }
1984 }
1985
Dake Gu6a20a192018-02-08 12:09:30 -08001986 private void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent keyEvent) {
1987 final View anchor = findView(id);
1988 if (anchor == null) {
1989 return;
1990 }
1991
Dake Gu6a20a192018-02-08 12:09:30 -08001992 synchronized (mLock) {
1993 if (mSessionId == sessionId) {
1994 AutofillClient client = getClient();
1995
1996 if (client != null) {
1997 client.autofillClientDispatchUnhandledKey(anchor, keyEvent);
1998 }
1999 }
2000 }
2001 }
2002
Felipe Leme51e29da2017-10-24 14:03:10 -07002003 /** @hide */
2004 public static final int SET_STATE_FLAG_ENABLED = 0x01;
2005 /** @hide */
2006 public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
2007 /** @hide */
2008 public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
2009 /** @hide */
2010 public static final int SET_STATE_FLAG_DEBUG = 0x08;
2011 /** @hide */
2012 public static final int SET_STATE_FLAG_VERBOSE = 0x10;
2013
2014 private void setState(int flags) {
2015 if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002016 synchronized (mLock) {
Felipe Leme51e29da2017-10-24 14:03:10 -07002017 mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
2018 if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
Svet Ganov48f10a22017-04-26 18:49:30 -07002019 // Reset the session state
Felipe Leme9a15c0f2018-02-14 17:26:46 -08002020 resetSessionLocked(/* resetEnteredIds= */ true);
Svet Ganov48f10a22017-04-26 18:49:30 -07002021 }
Felipe Leme51e29da2017-10-24 14:03:10 -07002022 if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
Svet Ganov48f10a22017-04-26 18:49:30 -07002023 // Reset connection to system
2024 mServiceClient = null;
Felipe Leme284ad1c2018-11-15 18:16:12 -08002025 mAugmentedAutofillServiceClient = null;
Koji Fukuiccec6a62017-10-18 17:48:40 +09002026 if (mServiceClientCleaner != null) {
2027 mServiceClientCleaner.clean();
2028 mServiceClientCleaner = null;
2029 }
Svet Ganov48f10a22017-04-26 18:49:30 -07002030 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002031 }
Felipe Leme51e29da2017-10-24 14:03:10 -07002032 sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
2033 sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002034 }
2035
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07002036 /**
2037 * Sets a view as autofilled if the current value is the {code targetValue}.
2038 *
2039 * @param view The view that is to be autofilled
2040 * @param targetValue The value we want to fill into view
2041 */
2042 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
2043 AutofillValue currentValue = view.getAutofillValue();
2044 if (Objects.equals(currentValue, targetValue)) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002045 synchronized (mLock) {
2046 if (mLastAutofilledData == null) {
2047 mLastAutofilledData = new ParcelableMap(1);
2048 }
Felipe Leme42b97932018-02-20 13:04:31 -08002049 mLastAutofilledData.put(view.getAutofillId(), targetValue);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07002050 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07002051 view.setAutofilled(true);
2052 }
2053 }
2054
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002055 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002056 synchronized (mLock) {
2057 if (sessionId != mSessionId) {
2058 return;
Felipe Leme4753bb02017-03-22 20:24:00 -07002059 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002060
Felipe Leme637e05e2017-12-06 12:09:37 -08002061 final AutofillClient client = getClient();
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002062 if (client == null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002063 return;
2064 }
2065
2066 final int itemCount = ids.size();
2067 int numApplied = 0;
2068 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002069 final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
2070 Helper.toArray(ids));
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002071
Felipe Leme49f08ed2018-03-26 16:18:45 -07002072 ArrayList<AutofillId> failedIds = null;
2073
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002074 for (int i = 0; i < itemCount; i++) {
2075 final AutofillId id = ids.get(i);
2076 final AutofillValue value = values.get(i);
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002077 final View view = views[i];
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002078 if (view == null) {
Felipe Leme49f08ed2018-03-26 16:18:45 -07002079 // Most likely view has been removed after the initial request was sent to the
2080 // the service; this is fine, but we need to update the view status in the
2081 // server side so it can be triggered again.
2082 Log.d(TAG, "autofill(): no View with id " + id);
2083 if (failedIds == null) {
2084 failedIds = new ArrayList<>();
2085 }
2086 failedIds.add(id);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002087 continue;
Felipe Leme4753bb02017-03-22 20:24:00 -07002088 }
Felipe Lemea75333c2019-01-22 15:45:32 -08002089 if (id.isVirtualInt()) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002090 if (virtualValues == null) {
2091 // Most likely there will be just one view with virtual children.
2092 virtualValues = new ArrayMap<>(1);
2093 }
2094 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
2095 if (valuesByParent == null) {
2096 // We don't know the size yet, but usually it will be just a few fields...
2097 valuesByParent = new SparseArray<>(5);
2098 virtualValues.put(view, valuesByParent);
2099 }
Felipe Lemea75333c2019-01-22 15:45:32 -08002100 valuesByParent.put(id.getVirtualChildIntId(), value);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002101 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002102 // Mark the view as to be autofilled with 'value'
2103 if (mLastAutofilledData == null) {
2104 mLastAutofilledData = new ParcelableMap(itemCount - i);
2105 }
2106 mLastAutofilledData.put(id, value);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002107
2108 view.autofill(value);
2109
2110 // Set as autofilled if the values match now, e.g. when the value was updated
2111 // synchronously.
2112 // If autofill happens async, the view is set to autofilled in
2113 // notifyValueChanged.
2114 setAutofilledIfValuesIs(view, value);
2115
2116 numApplied++;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -07002117 }
Felipe Leme4753bb02017-03-22 20:24:00 -07002118 }
Felipe Leme4753bb02017-03-22 20:24:00 -07002119
Felipe Leme49f08ed2018-03-26 16:18:45 -07002120 if (failedIds != null) {
2121 if (sVerbose) {
2122 Log.v(TAG, "autofill(): total failed views: " + failedIds);
2123 }
2124 try {
2125 mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
2126 } catch (RemoteException e) {
2127 // In theory, we could ignore this error since it's not a big deal, but
2128 // in reality, we rather crash the app anyways, as the failure could be
2129 // a consequence of something going wrong on the server side...
2130 e.rethrowFromSystemServer();
2131 }
2132 }
2133
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002134 if (virtualValues != null) {
2135 for (int i = 0; i < virtualValues.size(); i++) {
2136 final View parent = virtualValues.keyAt(i);
2137 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
2138 parent.autofill(childrenValues);
2139 numApplied += childrenValues.size();
Felipe Leme49f08ed2018-03-26 16:18:45 -07002140 // TODO: we should provide a callback so the parent can call failures; something
2141 // like notifyAutofillFailed(View view, int[] childrenIds);
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002142 }
Felipe Leme4753bb02017-03-22 20:24:00 -07002143 }
Felipe Leme4753bb02017-03-22 20:24:00 -07002144
Felipe Leme11166522018-05-07 10:18:47 -07002145 mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED)
Felipe Lemeb22d6352017-09-08 20:03:53 -07002146 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
Felipe Leme11166522018-05-07 10:18:47 -07002147 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied));
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002148 }
Felipe Leme4753bb02017-03-22 20:24:00 -07002149 }
2150
Felipe Leme11166522018-05-07 10:18:47 -07002151 private LogMaker newLog(int category) {
Felipe Lemeb838a092018-05-22 14:56:15 -07002152 final LogMaker log = new LogMaker(category)
Felipe Leme833c99b2018-05-24 10:41:48 -07002153 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, mSessionId);
2154
2155 if (isCompatibilityModeEnabledLocked()) {
2156 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1);
2157 }
Felipe Lemeb838a092018-05-22 14:56:15 -07002158 final AutofillClient client = getClient();
2159 if (client == null) {
2160 // Client should never be null here, but it doesn't hurt to check...
2161 log.setPackageName(mContext.getPackageName());
2162 } else {
2163 log.setComponentName(client.autofillClientGetComponentName());
2164 }
2165 return log;
Felipe Leme11166522018-05-07 10:18:47 -07002166 }
2167
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002168 /**
2169 * Set the tracked views.
2170 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002171 * @param trackedIds The views to be tracked.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002172 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002173 * @param saveOnFinish Finish the session once the activity is finished.
Felipe Leme27e20222017-05-18 15:24:11 -07002174 * @param fillableIds Views that might anchor FillUI.
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002175 * @param saveTriggerId View that when clicked triggers commit().
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002176 */
Felipe Leme27e20222017-05-18 15:24:11 -07002177 private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002178 boolean saveOnAllViewsInvisible, boolean saveOnFinish,
2179 @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002180 synchronized (mLock) {
Felipe Leme159cb002019-03-27 10:44:59 -07002181 if (sVerbose) {
2182 Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId
2183 + ", trackedIds=" + Arrays.toString(trackedIds)
2184 + ", saveOnAllViewsInvisible=" + saveOnAllViewsInvisible
2185 + ", saveOnFinish=" + saveOnFinish
2186 + ", fillableIds=" + Arrays.toString(fillableIds)
2187 + ", saveTrigerId=" + saveTriggerId
2188 + ", mFillableIds=" + mFillableIds
2189 + ", mEnabled=" + mEnabled
2190 + ", mSessionId=" + mSessionId);
2191
2192 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002193 if (mEnabled && mSessionId == sessionId) {
2194 if (saveOnAllViewsInvisible) {
2195 mTrackedViews = new TrackedViews(trackedIds);
2196 } else {
2197 mTrackedViews = null;
2198 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002199 mSaveOnFinish = saveOnFinish;
Felipe Leme27e20222017-05-18 15:24:11 -07002200 if (fillableIds != null) {
2201 if (mFillableIds == null) {
2202 mFillableIds = new ArraySet<>(fillableIds.length);
2203 }
2204 for (AutofillId id : fillableIds) {
2205 mFillableIds.add(id);
2206 }
Felipe Leme27e20222017-05-18 15:24:11 -07002207 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002208
2209 if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
2210 // Turn off trigger on previous view id.
2211 setNotifyOnClickLocked(mSaveTriggerId, false);
2212 }
2213
2214 if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
2215 // Turn on trigger on new view id.
2216 mSaveTriggerId = saveTriggerId;
2217 setNotifyOnClickLocked(mSaveTriggerId, true);
2218 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002219 }
2220 }
2221 }
2222
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002223 private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
2224 final View view = findView(id);
2225 if (view == null) {
2226 Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
2227 return;
2228 }
2229 view.setNotifyAutofillManagerOnClick(notify);
2230 }
2231
Felipe Lemec24a56a2017-08-03 14:27:57 -07002232 private void setSaveUiState(int sessionId, boolean shown) {
2233 if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
2234 synchronized (mLock) {
2235 if (mSessionId != NO_SESSION) {
2236 // Race condition: app triggered a new session after the previous session was
2237 // finished but before server called setSaveUiState() - need to cancel the new
2238 // session to avoid further inconsistent behavior.
2239 Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown
2240 + ") called on existing session " + mSessionId + "; cancelling it");
2241 cancelSessionLocked();
2242 }
2243 if (shown) {
2244 mSessionId = sessionId;
2245 mState = STATE_SHOWING_SAVE_UI;
2246 } else {
2247 mSessionId = NO_SESSION;
2248 mState = STATE_UNKNOWN;
2249 }
2250 }
2251 }
2252
Felipe Leme650f7ab2017-09-19 13:08:24 -07002253 /**
2254 * Marks the state of the session as finished.
2255 *
2256 * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
Felipe Leme0c8ce322018-03-23 13:54:22 -07002257 * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed),
2258 * {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar
Felipe Lemed9dc9542018-09-19 11:54:28 -07002259 * changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished
2260 * when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE}
2261 * (because the autofill service or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill
2262 * service disabled further autofill requests for the activity).
Felipe Leme6e43dd32019-03-13 17:25:33 -07002263 * @param autofillableIds list of ids that could trigger autofill, use to not handle a new
2264 * session when they're entered.
Felipe Leme650f7ab2017-09-19 13:08:24 -07002265 */
Felipe Leme6e43dd32019-03-13 17:25:33 -07002266 private void setSessionFinished(int newState, @Nullable List<AutofillId> autofillableIds) {
Felipe Lemec7b45292017-09-19 09:06:20 -07002267 synchronized (mLock) {
Felipe Leme0c8ce322018-03-23 13:54:22 -07002268 if (sVerbose) {
2269 Log.v(TAG, "setSessionFinished(): from " + getStateAsStringLocked() + " to "
Felipe Leme6e43dd32019-03-13 17:25:33 -07002270 + getStateAsString(newState) + "; autofillableIds=" + autofillableIds);
2271 }
2272 if (autofillableIds != null) {
2273 mEnteredIds = new ArraySet<>(autofillableIds);
Felipe Leme0c8ce322018-03-23 13:54:22 -07002274 }
Felipe Lemed9dc9542018-09-19 11:54:28 -07002275 if (newState == STATE_UNKNOWN_COMPAT_MODE || newState == STATE_UNKNOWN_FAILED) {
Felipe Leme0c8ce322018-03-23 13:54:22 -07002276 resetSessionLocked(/* resetEnteredIds= */ true);
2277 mState = STATE_UNKNOWN;
2278 } else {
2279 resetSessionLocked(/* resetEnteredIds= */ false);
2280 mState = newState;
2281 }
Felipe Lemec7b45292017-09-19 09:06:20 -07002282 }
2283 }
2284
Felipe Leme284ad1c2018-11-15 18:16:12 -08002285 /**
2286 * Gets a {@link AugmentedAutofillManagerClient} for this {@link AutofillManagerClient}.
2287 *
2288 * <p>These are 2 distinct objects because we need to restrict what the Augmented Autofill
2289 * service can do (which is defined by {@code IAugmentedAutofillManagerClient.aidl}).
2290 */
2291 private void getAugmentedAutofillClient(@NonNull IResultReceiver result) {
2292 synchronized (mLock) {
2293 if (mAugmentedAutofillServiceClient == null) {
2294 mAugmentedAutofillServiceClient = new AugmentedAutofillManagerClient(this);
2295 }
2296 final Bundle resultData = new Bundle();
2297 resultData.putBinder(EXTRA_AUGMENTED_AUTOFILL_CLIENT,
2298 mAugmentedAutofillServiceClient.asBinder());
2299
2300 try {
2301 result.send(0, resultData);
2302 } catch (RemoteException e) {
2303 Log.w(TAG, "Could not send AugmentedAutofillClient back: " + e);
2304 }
2305 }
2306 }
2307
Dake Gub0fa3782018-02-26 12:25:14 -08002308 /** @hide */
2309 public void requestHideFillUi() {
2310 requestHideFillUi(mIdShownFillUi, true);
2311 }
2312
2313 private void requestHideFillUi(AutofillId id, boolean force) {
2314 final View anchor = id == null ? null : findView(id);
Felipe Leme27e20222017-05-18 15:24:11 -07002315 if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002316 if (anchor == null) {
Dake Gub0fa3782018-02-26 12:25:14 -08002317 if (force) {
2318 // When user taps outside autofill window, force to close fill ui even id does
2319 // not match.
2320 AutofillClient client = getClient();
2321 if (client != null) {
2322 client.autofillClientRequestHideFillUi();
2323 }
2324 }
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002325 return;
2326 }
Felipe Leme27e20222017-05-18 15:24:11 -07002327 requestHideFillUi(id, anchor);
2328 }
2329
2330 private void requestHideFillUi(AutofillId id, View anchor) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002331
2332 AutofillCallback callback = null;
2333 synchronized (mLock) {
Svet Ganov48f10a22017-04-26 18:49:30 -07002334 // We do not check the session id for two reasons:
2335 // 1. If local and remote session id are off sync the UI would be stuck shown
2336 // 2. There is a race between the user state being destroyed due the fill
2337 // service being uninstalled and the UI being dismissed.
Felipe Leme637e05e2017-12-06 12:09:37 -08002338 AutofillClient client = getClient();
Svet Ganov48f10a22017-04-26 18:49:30 -07002339 if (client != null) {
Dake Gub0fa3782018-02-26 12:25:14 -08002340 if (client.autofillClientRequestHideFillUi()) {
2341 mIdShownFillUi = null;
Svet Ganov48f10a22017-04-26 18:49:30 -07002342 callback = mCallback;
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002343 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002344 }
2345 }
2346
2347 if (callback != null) {
Felipe Lemea75333c2019-01-22 15:45:32 -08002348 if (id.isVirtualInt()) {
2349 callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07002350 AutofillCallback.EVENT_INPUT_HIDDEN);
2351 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002352 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
Felipe Leme4753bb02017-03-22 20:24:00 -07002353 }
2354 }
2355 }
2356
Felipe Leme17292d12017-10-24 14:03:10 -07002357 private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
Felipe Lemec7b45292017-09-19 09:06:20 -07002358 if (sVerbose) {
2359 Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
Felipe Leme17292d12017-10-24 14:03:10 -07002360 + ", sessionFinishedState=" + sessionFinishedState);
Felipe Lemec7b45292017-09-19 09:06:20 -07002361 }
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002362 final View anchor = findView(id);
2363 if (anchor == null) {
2364 return;
2365 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002366
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002367 AutofillCallback callback = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002368 synchronized (mLock) {
Felipe Leme637e05e2017-12-06 12:09:37 -08002369 if (mSessionId == sessionId && getClient() != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07002370 callback = mCallback;
2371 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002372 }
2373
2374 if (callback != null) {
Felipe Lemea75333c2019-01-22 15:45:32 -08002375 if (id.isVirtualInt()) {
2376 callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07002377 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
2378 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002379 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Felipe Leme4753bb02017-03-22 20:24:00 -07002380 }
Felipe Lemec7b45292017-09-19 09:06:20 -07002381 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07002382
Felipe Leme17292d12017-10-24 14:03:10 -07002383 if (sessionFinishedState != 0) {
Felipe Lemec7b45292017-09-19 09:06:20 -07002384 // Callback call was "hijacked" to also update the session state.
Felipe Leme6e43dd32019-03-13 17:25:33 -07002385 setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
Felipe Leme4753bb02017-03-22 20:24:00 -07002386 }
2387 }
2388
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002389 /**
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002390 * Find a single view by its id.
2391 *
2392 * @param autofillId The autofill id of the view
2393 *
2394 * @return The view or {@code null} if view was not found
2395 */
2396 private View findView(@NonNull AutofillId autofillId) {
Felipe Leme637e05e2017-12-06 12:09:37 -08002397 final AutofillClient client = getClient();
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002398 if (client != null) {
2399 return client.autofillClientFindViewByAutofillIdTraversal(autofillId);
Felipe Lemee6010f22017-03-03 11:19:51 -08002400 }
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002401 return null;
Felipe Lemee6010f22017-03-03 11:19:51 -08002402 }
2403
Felipe Lemebb810922017-04-25 15:54:06 -07002404 /** @hide */
2405 public boolean hasAutofillFeature() {
Svet Ganov43574b02017-04-12 09:25:20 -07002406 return mService != null;
2407 }
2408
Felipe Lemec24a56a2017-08-03 14:27:57 -07002409 /** @hide */
2410 public void onPendingSaveUi(int operation, IBinder token) {
2411 if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
2412
2413 synchronized (mLock) {
2414 try {
2415 mService.onPendingSaveUi(operation, token);
2416 } catch (RemoteException e) {
2417 e.rethrowFromSystemServer();
2418 }
2419 }
2420 }
2421
2422 /** @hide */
2423 public void dump(String outerPrefix, PrintWriter pw) {
2424 pw.print(outerPrefix); pw.println("AutofillManager:");
2425 final String pfx = outerPrefix + " ";
2426 pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
Felipe Lemec7b45292017-09-19 09:06:20 -07002427 pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
Felipe Leme686128e2017-10-17 14:02:20 -07002428 pw.print(pfx); pw.print("context: "); pw.println(mContext);
Felipe Lemeb546ca72018-08-15 08:44:12 -07002429 final AutofillClient client = getClient();
2430 if (client != null) {
2431 pw.print(pfx); pw.print("client: "); pw.print(client);
2432 pw.print(" ("); pw.print(client.autofillClientGetActivityToken()); pw.println(')');
2433 }
Felipe Lemec24a56a2017-08-03 14:27:57 -07002434 pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
Felipe Lemedf30f272019-03-19 13:47:48 -07002435 pw.print(pfx); pw.print("enabledAugmentedOnly: "); pw.println(mForAugmentedAutofillOnly);
Felipe Lemec24a56a2017-08-03 14:27:57 -07002436 pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
2437 pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
Dake Gu67decfa2017-12-27 11:48:08 -08002438 pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled);
Felipe Lemec24a56a2017-08-03 14:27:57 -07002439 pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData);
Felipe Lemef5e26302018-06-08 16:26:39 -07002440 pw.print(pfx); pw.print("id of last fill UI shown: "); pw.println(mIdShownFillUi);
Felipe Lemec24a56a2017-08-03 14:27:57 -07002441 pw.print(pfx); pw.print("tracked views: ");
2442 if (mTrackedViews == null) {
2443 pw.println("null");
2444 } else {
2445 final String pfx2 = pfx + " ";
2446 pw.println();
2447 pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds);
2448 pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
2449 }
2450 pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
Felipe Leme9a15c0f2018-02-14 17:26:46 -08002451 pw.print(pfx); pw.print("entered ids: "); pw.println(mEnteredIds);
Felipe Lemea7de4022019-02-19 17:16:45 -08002452 if (mEnteredForAugmentedAutofillIds != null) {
2453 pw.print(pfx); pw.print("entered ids for augmented autofill: ");
2454 pw.println(mEnteredForAugmentedAutofillIds);
2455 }
Felipe Lemeadb34f52019-03-15 16:24:26 -07002456 if (mForAugmentedAutofillOnly) {
2457 pw.print(pfx); pw.println("For Augmented Autofill Only");
2458 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -07002459 pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
2460 pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
Felipe Lemea4f39cd2019-02-19 15:08:59 -08002461 if (mOptions != null) {
2462 pw.print(pfx); pw.print("options: "); mOptions.dumpShort(pw); pw.println();
2463 }
Felipe Lemeb546ca72018-08-15 08:44:12 -07002464 pw.print(pfx); pw.print("compat mode enabled: ");
2465 synchronized (mLock) {
2466 if (mCompatibilityBridge != null) {
2467 final String pfx2 = pfx + " ";
2468 pw.println("true");
2469 pw.print(pfx2); pw.print("windowId: ");
2470 pw.println(mCompatibilityBridge.mFocusedWindowId);
2471 pw.print(pfx2); pw.print("nodeId: ");
2472 pw.println(mCompatibilityBridge.mFocusedNodeId);
2473 pw.print(pfx2); pw.print("virtualId: ");
2474 pw.println(AccessibilityNodeInfo
2475 .getVirtualDescendantId(mCompatibilityBridge.mFocusedNodeId));
2476 pw.print(pfx2); pw.print("focusedBounds: ");
2477 pw.println(mCompatibilityBridge.mFocusedBounds);
2478 } else {
2479 pw.println("false");
2480 }
2481 }
Felipe Leme51e29da2017-10-24 14:03:10 -07002482 pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
2483 pw.print(" verbose: "); pw.println(sVerbose);
Felipe Lemec24a56a2017-08-03 14:27:57 -07002484 }
2485
Andreas Gampe3f24e692018-02-05 13:24:28 -08002486 @GuardedBy("mLock")
Felipe Lemec7b45292017-09-19 09:06:20 -07002487 private String getStateAsStringLocked() {
Felipe Leme0c8ce322018-03-23 13:54:22 -07002488 return getStateAsString(mState);
2489 }
2490
2491 @NonNull
2492 private static String getStateAsString(int state) {
2493 switch (state) {
Felipe Lemec7b45292017-09-19 09:06:20 -07002494 case STATE_UNKNOWN:
Felipe Leme0c8ce322018-03-23 13:54:22 -07002495 return "UNKNOWN";
Felipe Lemec7b45292017-09-19 09:06:20 -07002496 case STATE_ACTIVE:
Felipe Leme0c8ce322018-03-23 13:54:22 -07002497 return "ACTIVE";
Felipe Lemec7b45292017-09-19 09:06:20 -07002498 case STATE_FINISHED:
Felipe Leme0c8ce322018-03-23 13:54:22 -07002499 return "FINISHED";
Felipe Lemec7b45292017-09-19 09:06:20 -07002500 case STATE_SHOWING_SAVE_UI:
Felipe Leme0c8ce322018-03-23 13:54:22 -07002501 return "SHOWING_SAVE_UI";
Felipe Leme17292d12017-10-24 14:03:10 -07002502 case STATE_DISABLED_BY_SERVICE:
Felipe Leme0c8ce322018-03-23 13:54:22 -07002503 return "DISABLED_BY_SERVICE";
2504 case STATE_UNKNOWN_COMPAT_MODE:
2505 return "UNKNOWN_COMPAT_MODE";
Felipe Lemed9dc9542018-09-19 11:54:28 -07002506 case STATE_UNKNOWN_FAILED:
2507 return "UNKNOWN_FAILED";
Felipe Lemec7b45292017-09-19 09:06:20 -07002508 default:
Felipe Leme0c8ce322018-03-23 13:54:22 -07002509 return "INVALID:" + state;
Felipe Lemec7b45292017-09-19 09:06:20 -07002510 }
2511 }
2512
Felipe Leme559e21d2019-01-18 17:57:21 -08002513 /** @hide */
2514 public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) {
Felipe Lemecc510272019-02-07 14:56:27 -08002515 switch (flags) {
2516 case FLAG_SMART_SUGGESTION_OFF:
2517 return "OFF";
2518 case FLAG_SMART_SUGGESTION_SYSTEM:
2519 return "SYSTEM";
2520 default:
2521 return "INVALID:" + flags;
2522 }
Felipe Leme559e21d2019-01-18 17:57:21 -08002523 }
2524
Andreas Gampe3f24e692018-02-05 13:24:28 -08002525 @GuardedBy("mLock")
Felipe Lemec24a56a2017-08-03 14:27:57 -07002526 private boolean isActiveLocked() {
2527 return mState == STATE_ACTIVE;
2528 }
2529
Andreas Gampe3f24e692018-02-05 13:24:28 -08002530 @GuardedBy("mLock")
Felipe Leme3103c632017-12-18 15:05:14 -08002531 private boolean isDisabledByServiceLocked() {
Felipe Leme17292d12017-10-24 14:03:10 -07002532 return mState == STATE_DISABLED_BY_SERVICE;
Felipe Lemec7b45292017-09-19 09:06:20 -07002533 }
2534
Andreas Gampe3f24e692018-02-05 13:24:28 -08002535 @GuardedBy("mLock")
Felipe Leme3103c632017-12-18 15:05:14 -08002536 private boolean isFinishedLocked() {
2537 return mState == STATE_FINISHED;
2538 }
2539
Felipe Leme9876a6f2017-05-30 15:47:28 -07002540 private void post(Runnable runnable) {
Felipe Leme637e05e2017-12-06 12:09:37 -08002541 final AutofillClient client = getClient();
Felipe Leme9876a6f2017-05-30 15:47:28 -07002542 if (client == null) {
2543 if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
2544 return;
2545 }
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002546 client.autofillClientRunOnUiThread(runnable);
2547 }
2548
2549 /**
2550 * Implementation of the accessibility based compatibility.
2551 */
2552 private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy {
2553 @GuardedBy("mLock")
2554 private final Rect mFocusedBounds = new Rect();
2555 @GuardedBy("mLock")
2556 private final Rect mTempBounds = new Rect();
2557
2558 @GuardedBy("mLock")
2559 private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
2560 @GuardedBy("mLock")
2561 private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
2562
2563 // Need to report a fake service in case a11y clients check the service list
2564 @NonNull
2565 @GuardedBy("mLock")
2566 AccessibilityServiceInfo mCompatServiceInfo;
2567
2568 CompatibilityBridge() {
2569 final AccessibilityManager am = AccessibilityManager.getInstance(mContext);
2570 am.setAccessibilityPolicy(this);
2571 }
2572
2573 private AccessibilityServiceInfo getCompatServiceInfo() {
2574 synchronized (mLock) {
2575 if (mCompatServiceInfo != null) {
2576 return mCompatServiceInfo;
2577 }
2578 final Intent intent = new Intent();
2579 intent.setComponent(new ComponentName("android",
2580 "com.android.server.autofill.AutofillCompatAccessibilityService"));
2581 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(
2582 intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
2583 try {
2584 mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
2585 } catch (XmlPullParserException | IOException e) {
2586 Log.e(TAG, "Cannot find compat autofill service:" + intent);
2587 throw new IllegalStateException("Cannot find compat autofill service");
2588 }
2589 return mCompatServiceInfo;
2590 }
2591 }
2592
2593 @Override
2594 public boolean isEnabled(boolean accessibilityEnabled) {
2595 return true;
2596 }
2597
2598 @Override
2599 public int getRelevantEventTypes(int relevantEventTypes) {
2600 return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED
2601 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
Felipe Leme54cc6832018-03-06 12:54:31 -08002602 | AccessibilityEvent.TYPE_VIEW_CLICKED
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002603 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
2604 }
2605
2606 @Override
2607 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
2608 List<AccessibilityServiceInfo> installedServices) {
2609 if (installedServices == null) {
2610 installedServices = new ArrayList<>();
2611 }
2612 installedServices.add(getCompatServiceInfo());
2613 return installedServices;
2614 }
2615
2616 @Override
2617 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
2618 int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) {
2619 if (enabledService == null) {
2620 enabledService = new ArrayList<>();
2621 }
2622 enabledService.add(getCompatServiceInfo());
2623 return enabledService;
2624 }
2625
2626 @Override
2627 public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event,
2628 boolean accessibilityEnabled, int relevantEventTypes) {
Felipe Leme5d35d3d2018-08-13 18:15:32 -07002629 final int type = event.getEventType();
2630 if (sVerbose) {
2631 // NOTE: this is waaay spammy, but that's life.
2632 Log.v(TAG, "onAccessibilityEvent(" + AccessibilityEvent.eventTypeToString(type)
Felipe Leme7d4461d2018-08-16 10:49:03 -07002633 + "): virtualId="
2634 + AccessibilityNodeInfo.getVirtualDescendantId(event.getSourceNodeId())
2635 + ", client=" + getClient());
Felipe Leme5d35d3d2018-08-13 18:15:32 -07002636 }
2637 switch (type) {
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002638 case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
2639 synchronized (mLock) {
2640 if (mFocusedWindowId == event.getWindowId()
2641 && mFocusedNodeId == event.getSourceNodeId()) {
2642 return event;
2643 }
2644 if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
2645 && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) {
2646 notifyViewExited(mFocusedWindowId, mFocusedNodeId);
2647 mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
2648 mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
2649 mFocusedBounds.set(0, 0, 0, 0);
2650 }
2651 final int windowId = event.getWindowId();
2652 final long nodeId = event.getSourceNodeId();
2653 if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) {
2654 mFocusedWindowId = windowId;
2655 mFocusedNodeId = nodeId;
2656 }
2657 }
2658 } break;
2659
2660 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: {
2661 synchronized (mLock) {
2662 if (mFocusedWindowId == event.getWindowId()
2663 && mFocusedNodeId == event.getSourceNodeId()) {
2664 notifyValueChanged(event.getWindowId(), event.getSourceNodeId());
2665 }
2666 }
2667 } break;
2668
Felipe Leme54cc6832018-03-06 12:54:31 -08002669 case AccessibilityEvent.TYPE_VIEW_CLICKED: {
2670 synchronized (mLock) {
2671 notifyViewClicked(event.getWindowId(), event.getSourceNodeId());
2672 }
2673 } break;
2674
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002675 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
2676 final AutofillClient client = getClient();
2677 if (client != null) {
2678 synchronized (mLock) {
2679 if (client.autofillClientIsFillUiShowing()) {
2680 notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds);
2681 }
2682 updateTrackedViewsLocked();
2683 }
2684 }
2685 } break;
2686 }
2687
2688 return accessibilityEnabled ? event : null;
2689 }
2690
2691 private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) {
2692 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
2693 if (!isVirtualNode(virtualId)) {
2694 return false;
2695 }
2696 final View view = findViewByAccessibilityId(windowId, nodeId);
2697 if (view == null) {
2698 return false;
2699 }
2700 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
2701 if (node == null) {
2702 return false;
2703 }
2704 if (!node.isEditable()) {
2705 return false;
2706 }
2707 final Rect newBounds = mTempBounds;
2708 node.getBoundsInScreen(newBounds);
2709 if (newBounds.equals(focusedBounds)) {
2710 return false;
2711 }
2712 focusedBounds.set(newBounds);
2713 AutofillManager.this.notifyViewEntered(view, virtualId, newBounds);
2714 return true;
2715 }
2716
2717 private void notifyViewExited(int windowId, long nodeId) {
2718 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
2719 if (!isVirtualNode(virtualId)) {
2720 return;
2721 }
2722 final View view = findViewByAccessibilityId(windowId, nodeId);
2723 if (view == null) {
2724 return;
2725 }
2726 AutofillManager.this.notifyViewExited(view, virtualId);
2727 }
2728
2729 private void notifyValueChanged(int windowId, long nodeId) {
2730 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
2731 if (!isVirtualNode(virtualId)) {
2732 return;
2733 }
2734 final View view = findViewByAccessibilityId(windowId, nodeId);
2735 if (view == null) {
2736 return;
2737 }
2738 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
2739 if (node == null) {
2740 return;
2741 }
2742 AutofillManager.this.notifyValueChanged(view, virtualId,
2743 AutofillValue.forText(node.getText()));
2744 }
2745
Felipe Leme54cc6832018-03-06 12:54:31 -08002746 private void notifyViewClicked(int windowId, long nodeId) {
2747 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
2748 if (!isVirtualNode(virtualId)) {
2749 return;
2750 }
2751 final View view = findViewByAccessibilityId(windowId, nodeId);
2752 if (view == null) {
2753 return;
2754 }
2755 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
2756 if (node == null) {
2757 return;
2758 }
2759 AutofillManager.this.notifyViewClicked(view, virtualId);
2760 }
2761
Andreas Gampe3f24e692018-02-05 13:24:28 -08002762 @GuardedBy("mLock")
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002763 private void updateTrackedViewsLocked() {
2764 if (mTrackedViews != null) {
2765 mTrackedViews.onVisibleForAutofillChangedLocked();
2766 }
2767 }
2768
2769 private View findViewByAccessibilityId(int windowId, long nodeId) {
2770 final AutofillClient client = getClient();
2771 if (client == null) {
2772 return null;
2773 }
2774 final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId);
2775 return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId);
2776 }
2777
2778 private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) {
2779 final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
2780 if (provider == null) {
2781 return null;
2782 }
2783 return provider.createAccessibilityNodeInfo(virtualId);
2784 }
2785
2786 private boolean isVirtualNode(int nodeId) {
2787 return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID
2788 && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
2789 }
Felipe Leme9876a6f2017-05-30 15:47:28 -07002790 }
2791
Felipe Lemee6010f22017-03-03 11:19:51 -08002792 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002793 * View tracking information. Once all tracked views become invisible the session is finished.
2794 */
2795 private class TrackedViews {
2796 /** Visible tracked views */
2797 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
2798
2799 /** Invisible tracked views */
2800 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
2801
2802 /**
2803 * Check if set is null or value is in set.
2804 *
2805 * @param set The set or null (== empty set)
2806 * @param value The value that might be in the set
2807 *
2808 * @return {@code true} iff set is not empty and value is in set
2809 */
Felipe Leme27e20222017-05-18 15:24:11 -07002810 // TODO: move to Helper as static method
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002811 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
2812 return set != null && set.contains(value);
2813 }
2814
2815 /**
2816 * Add a value to a set. If set is null, create a new set.
2817 *
2818 * @param set The set or null (== empty set)
2819 * @param valueToAdd The value to add
2820 *
2821 * @return The set including the new value. If set was {@code null}, a set containing only
2822 * the new value.
2823 */
Felipe Leme27e20222017-05-18 15:24:11 -07002824 // TODO: move to Helper as static method
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002825 @NonNull
2826 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
2827 if (set == null) {
2828 set = new ArraySet<>(1);
2829 }
2830
2831 set.add(valueToAdd);
2832
2833 return set;
2834 }
2835
2836 /**
2837 * Remove a value from a set.
2838 *
2839 * @param set The set or null (== empty set)
2840 * @param valueToRemove The value to remove
2841 *
2842 * @return The set without the removed value. {@code null} if set was null, or is empty
2843 * after removal.
2844 */
Felipe Leme27e20222017-05-18 15:24:11 -07002845 // TODO: move to Helper as static method
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002846 @Nullable
2847 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
2848 if (set == null) {
2849 return null;
2850 }
2851
2852 set.remove(valueToRemove);
2853
2854 if (set.isEmpty()) {
2855 return null;
2856 }
2857
2858 return set;
2859 }
2860
2861 /**
2862 * Set the tracked views.
2863 *
2864 * @param trackedIds The views to be tracked
2865 */
Felipe Leme27e20222017-05-18 15:24:11 -07002866 TrackedViews(@Nullable AutofillId[] trackedIds) {
Felipe Leme637e05e2017-12-06 12:09:37 -08002867 final AutofillClient client = getClient();
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002868 if (!ArrayUtils.isEmpty(trackedIds) && client != null) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002869 final boolean[] isVisible;
2870
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002871 if (client.autofillClientIsVisibleForAutofill()) {
Felipe Leme5b32ebe2018-02-15 12:52:19 -08002872 if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002873 isVisible = client.autofillClientGetViewVisibility(trackedIds);
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002874 } else {
2875 // All false
Felipe Leme27e20222017-05-18 15:24:11 -07002876 isVisible = new boolean[trackedIds.length];
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002877 }
2878
Felipe Leme27e20222017-05-18 15:24:11 -07002879 final int numIds = trackedIds.length;
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002880 for (int i = 0; i < numIds; i++) {
Felipe Leme27e20222017-05-18 15:24:11 -07002881 final AutofillId id = trackedIds[i];
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002882
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002883 if (isVisible[i]) {
Philip P. Moltmanne0e28712017-04-20 15:19:06 -07002884 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002885 } else {
2886 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
2887 }
2888 }
2889 }
2890
Felipe Leme9f9ee252017-04-27 13:56:22 -07002891 if (sVerbose) {
Felipe Leme5b32ebe2018-02-15 12:52:19 -08002892 Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): "
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002893 + " mVisibleTrackedIds=" + mVisibleTrackedIds
2894 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
2895 }
2896
2897 if (mVisibleTrackedIds == null) {
2898 finishSessionLocked();
2899 }
2900 }
2901
2902 /**
2903 * Called when a {@link View view's} visibility changes.
2904 *
Svetoslav Ganov4db89ba2017-06-28 16:50:37 -07002905 * @param id the id of the view/virtual view whose visibility changed.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002906 * @param isVisible visible if the view is visible in the view hierarchy.
2907 */
Andreas Gampe3f24e692018-02-05 13:24:28 -08002908 @GuardedBy("mLock")
Dake Gu67decfa2017-12-27 11:48:08 -08002909 void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) {
Felipe Leme9f9ee252017-04-27 13:56:22 -07002910 if (sDebug) {
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002911 Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible="
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002912 + isVisible);
2913 }
2914
Dake Gu67decfa2017-12-27 11:48:08 -08002915 if (isClientVisibleForAutofillLocked()) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002916 if (isVisible) {
2917 if (isInSet(mInvisibleTrackedIds, id)) {
2918 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
2919 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
2920 }
2921 } else {
2922 if (isInSet(mVisibleTrackedIds, id)) {
2923 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
2924 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
2925 }
2926 }
2927 }
2928
2929 if (mVisibleTrackedIds == null) {
Felipe Leme27e20222017-05-18 15:24:11 -07002930 if (sVerbose) {
2931 Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds);
2932 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002933 finishSessionLocked();
2934 }
2935 }
2936
2937 /**
2938 * Called once the client becomes visible.
2939 *
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002940 * @see AutofillClient#autofillClientIsVisibleForAutofill()
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002941 */
Andreas Gampe3f24e692018-02-05 13:24:28 -08002942 @GuardedBy("mLock")
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002943 void onVisibleForAutofillChangedLocked() {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002944 // The visibility of the views might have changed while the client was not be visible,
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002945 // hence update the visibility state for all views.
Felipe Leme637e05e2017-12-06 12:09:37 -08002946 AutofillClient client = getClient();
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002947 ArraySet<AutofillId> updatedVisibleTrackedIds = null;
2948 ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
2949 if (client != null) {
Felipe Leme7008e702018-03-16 18:02:16 -07002950 if (sVerbose) {
2951 Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds
2952 + " vis=" + mVisibleTrackedIds);
2953 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002954 if (mInvisibleTrackedIds != null) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002955 final ArrayList<AutofillId> orderedInvisibleIds =
2956 new ArrayList<>(mInvisibleTrackedIds);
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002957 final boolean[] isVisible = client.autofillClientGetViewVisibility(
2958 Helper.toArray(orderedInvisibleIds));
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002959
2960 final int numInvisibleTrackedIds = orderedInvisibleIds.size();
2961 for (int i = 0; i < numInvisibleTrackedIds; i++) {
2962 final AutofillId id = orderedInvisibleIds.get(i);
2963 if (isVisible[i]) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002964 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
2965
Felipe Leme9f9ee252017-04-27 13:56:22 -07002966 if (sDebug) {
2967 Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002968 }
2969 } else {
2970 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
2971 }
2972 }
2973 }
2974
2975 if (mVisibleTrackedIds != null) {
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002976 final ArrayList<AutofillId> orderedVisibleIds =
2977 new ArrayList<>(mVisibleTrackedIds);
Svetoslav Ganov24c90452017-12-27 15:17:14 -08002978 final boolean[] isVisible = client.autofillClientGetViewVisibility(
2979 Helper.toArray(orderedVisibleIds));
Philip P. Moltmann134cee22017-05-06 11:28:38 -07002980
2981 final int numVisibleTrackedIds = orderedVisibleIds.size();
2982 for (int i = 0; i < numVisibleTrackedIds; i++) {
2983 final AutofillId id = orderedVisibleIds.get(i);
2984
2985 if (isVisible[i]) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002986 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
2987 } else {
2988 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
2989
Felipe Leme9f9ee252017-04-27 13:56:22 -07002990 if (sDebug) {
2991 Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07002992 }
2993 }
2994 }
2995 }
2996
2997 mInvisibleTrackedIds = updatedInvisibleTrackedIds;
2998 mVisibleTrackedIds = updatedVisibleTrackedIds;
2999 }
3000
3001 if (mVisibleTrackedIds == null) {
Felipe Leme5b32ebe2018-02-15 12:52:19 -08003002 if (sVerbose) {
3003 Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
3004 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07003005 finishSessionLocked();
3006 }
3007 }
3008 }
3009
3010 /**
Felipe Leme744976e2017-07-31 11:34:14 -07003011 * Callback for autofill related events.
Felipe Lemee6010f22017-03-03 11:19:51 -08003012 *
3013 * <p>Typically used for applications that display their own "auto-complete" views, so they can
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003014 * enable / disable such views when the autofill UI is shown / hidden.
Felipe Lemee6010f22017-03-03 11:19:51 -08003015 */
3016 public abstract static class AutofillCallback {
3017
3018 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -07003019 @IntDef(prefix = { "EVENT_INPUT_" }, value = {
3020 EVENT_INPUT_SHOWN,
3021 EVENT_INPUT_HIDDEN,
3022 EVENT_INPUT_UNAVAILABLE
3023 })
Felipe Lemee6010f22017-03-03 11:19:51 -08003024 @Retention(RetentionPolicy.SOURCE)
3025 public @interface AutofillEventType {}
3026
3027 /**
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003028 * The autofill input UI associated with the view was shown.
Felipe Lemee6010f22017-03-03 11:19:51 -08003029 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003030 * <p>If the view provides its own auto-complete UI and its currently shown, it
Felipe Lemee6010f22017-03-03 11:19:51 -08003031 * should be hidden upon receiving this event.
3032 */
3033 public static final int EVENT_INPUT_SHOWN = 1;
3034
3035 /**
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003036 * The autofill input UI associated with the view was hidden.
Felipe Lemee6010f22017-03-03 11:19:51 -08003037 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003038 * <p>If the view provides its own auto-complete UI that was hidden upon a
Felipe Lemee6010f22017-03-03 11:19:51 -08003039 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
3040 */
3041 public static final int EVENT_INPUT_HIDDEN = 2;
3042
3043 /**
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003044 * The autofill input UI associated with the view isn't shown because
Felipe Leme24aae152017-03-15 12:33:01 -07003045 * autofill is not available.
3046 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003047 * <p>If the view provides its own auto-complete UI but was not displaying it
Felipe Leme24aae152017-03-15 12:33:01 -07003048 * to avoid flickering, it could shown it upon receiving this event.
3049 */
3050 public static final int EVENT_INPUT_UNAVAILABLE = 3;
3051
3052 /**
Felipe Lemee6010f22017-03-03 11:19:51 -08003053 * Called after a change in the autofill state associated with a view.
3054 *
3055 * @param view view associated with the change.
3056 *
3057 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
3058 */
Felipe Leme81f01d92017-03-16 17:13:25 -07003059 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
3060 }
Felipe Lemee6010f22017-03-03 11:19:51 -08003061
3062 /**
3063 * Called after a change in the autofill state associated with a virtual view.
3064 *
3065 * @param view parent view associated with the change.
Felipe Leme6dcec872017-05-25 11:24:23 -07003066 * @param virtualId id identifying the virtual child inside the parent view.
Felipe Lemee6010f22017-03-03 11:19:51 -08003067 *
3068 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
3069 */
Felipe Leme6dcec872017-05-25 11:24:23 -07003070 public void onAutofillEvent(@NonNull View view, int virtualId,
3071 @AutofillEventType int event) {
Felipe Leme81f01d92017-03-16 17:13:25 -07003072 }
Felipe Lemee6010f22017-03-03 11:19:51 -08003073 }
3074
Felipe Leme640f30a2017-03-06 15:44:06 -08003075 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
3076 private final WeakReference<AutofillManager> mAfm;
Svet Ganov782043c2017-02-11 00:52:02 +00003077
Felipe Leme284ad1c2018-11-15 18:16:12 -08003078 private AutofillManagerClient(AutofillManager autofillManager) {
Felipe Leme640f30a2017-03-06 15:44:06 -08003079 mAfm = new WeakReference<>(autofillManager);
Svet Ganov782043c2017-02-11 00:52:02 +00003080 }
3081
3082 @Override
Felipe Leme51e29da2017-10-24 14:03:10 -07003083 public void setState(int flags) {
Felipe Leme640f30a2017-03-06 15:44:06 -08003084 final AutofillManager afm = mAfm.get();
3085 if (afm != null) {
Felipe Leme51e29da2017-10-24 14:03:10 -07003086 afm.post(() -> afm.setState(flags));
Svet Ganov782043c2017-02-11 00:52:02 +00003087 }
3088 }
3089
3090 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07003091 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
Felipe Leme640f30a2017-03-06 15:44:06 -08003092 final AutofillManager afm = mAfm.get();
3093 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07003094 afm.post(() -> afm.autofill(sessionId, ids, values));
Svet Ganov782043c2017-02-11 00:52:02 +00003095 }
3096 }
3097
3098 @Override
Svetoslav Ganova9379d02017-05-09 17:40:24 -07003099 public void authenticate(int sessionId, int authenticationId, IntentSender intent,
3100 Intent fillInIntent) {
Felipe Leme640f30a2017-03-06 15:44:06 -08003101 final AutofillManager afm = mAfm.get();
3102 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07003103 afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
Svet Ganov782043c2017-02-11 00:52:02 +00003104 }
3105 }
Felipe Lemee6010f22017-03-03 11:19:51 -08003106
3107 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07003108 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
3109 Rect anchorBounds, IAutofillWindowPresenter presenter) {
Felipe Leme640f30a2017-03-06 15:44:06 -08003110 final AutofillManager afm = mAfm.get();
3111 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07003112 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
3113 presenter));
Felipe Leme4753bb02017-03-22 20:24:00 -07003114 }
3115 }
3116
3117 @Override
Philip P. Moltmann134cee22017-05-06 11:28:38 -07003118 public void requestHideFillUi(int sessionId, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07003119 final AutofillManager afm = mAfm.get();
3120 if (afm != null) {
Dake Gub0fa3782018-02-26 12:25:14 -08003121 afm.post(() -> afm.requestHideFillUi(id, false));
Felipe Leme4753bb02017-03-22 20:24:00 -07003122 }
3123 }
3124
3125 @Override
Felipe Leme17292d12017-10-24 14:03:10 -07003126 public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
Felipe Leme4753bb02017-03-22 20:24:00 -07003127 final AutofillManager afm = mAfm.get();
3128 if (afm != null) {
Felipe Leme17292d12017-10-24 14:03:10 -07003129 afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState));
Felipe Lemee6010f22017-03-03 11:19:51 -08003130 }
3131 }
Svet Ganovc3d1c852017-04-12 22:34:28 -07003132
3133 @Override
Dake Gu6a20a192018-02-08 12:09:30 -08003134 public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) {
3135 final AutofillManager afm = mAfm.get();
3136 if (afm != null) {
3137 afm.post(() -> afm.dispatchUnhandledKey(sessionId, id, fullScreen));
3138 }
3139 }
3140
3141 @Override
Felipe Lemec24a56a2017-08-03 14:27:57 -07003142 public void startIntentSender(IntentSender intentSender, Intent intent) {
Svet Ganovc3d1c852017-04-12 22:34:28 -07003143 final AutofillManager afm = mAfm.get();
3144 if (afm != null) {
Felipe Leme9876a6f2017-05-30 15:47:28 -07003145 afm.post(() -> {
Svet Ganovc3d1c852017-04-12 22:34:28 -07003146 try {
Felipe Lemec24a56a2017-08-03 14:27:57 -07003147 afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0);
Svet Ganovc3d1c852017-04-12 22:34:28 -07003148 } catch (IntentSender.SendIntentException e) {
3149 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
3150 }
3151 });
3152 }
3153 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07003154
3155 @Override
Felipe Leme27e20222017-05-18 15:24:11 -07003156 public void setTrackedViews(int sessionId, AutofillId[] ids,
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003157 boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
3158 AutofillId saveTriggerId) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07003159 final AutofillManager afm = mAfm.get();
3160 if (afm != null) {
Felipe Leme2fe3ade2017-09-28 15:03:36 -07003161 afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
3162 saveOnFinish, fillableIds, saveTriggerId));
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07003163 }
3164 }
Felipe Lemec24a56a2017-08-03 14:27:57 -07003165
3166 @Override
3167 public void setSaveUiState(int sessionId, boolean shown) {
3168 final AutofillManager afm = mAfm.get();
3169 if (afm != null) {
Felipe Lemec7b45292017-09-19 09:06:20 -07003170 afm.post(() -> afm.setSaveUiState(sessionId, shown));
3171 }
3172 }
3173
3174 @Override
Felipe Leme6e43dd32019-03-13 17:25:33 -07003175 public void setSessionFinished(int newState, List<AutofillId> autofillableIds) {
Felipe Lemec7b45292017-09-19 09:06:20 -07003176 final AutofillManager afm = mAfm.get();
3177 if (afm != null) {
Felipe Leme6e43dd32019-03-13 17:25:33 -07003178 afm.post(() -> afm.setSessionFinished(newState, autofillableIds));
Felipe Lemec24a56a2017-08-03 14:27:57 -07003179 }
3180 }
Felipe Leme284ad1c2018-11-15 18:16:12 -08003181
3182 @Override
3183 public void getAugmentedAutofillClient(IResultReceiver result) {
3184 final AutofillManager afm = mAfm.get();
3185 if (afm != null) {
3186 afm.post(() -> afm.getAugmentedAutofillClient(result));
3187 }
3188 }
3189 }
3190
3191 private static final class AugmentedAutofillManagerClient
3192 extends IAugmentedAutofillManagerClient.Stub {
3193 private final WeakReference<AutofillManager> mAfm;
3194
3195 private AugmentedAutofillManagerClient(AutofillManager autofillManager) {
3196 mAfm = new WeakReference<>(autofillManager);
3197 }
3198
3199 @Override
3200 public Rect getViewCoordinates(@NonNull AutofillId id) {
Felipe Leme284ad1c2018-11-15 18:16:12 -08003201 final AutofillManager afm = mAfm.get();
3202 if (afm == null) return null;
3203
3204 final View view = afm.getClient().autofillClientFindViewByAutofillIdTraversal(id);
Adam He234030a2019-03-15 15:57:57 -07003205 if (view == null) {
3206 Log.w(TAG, "getViewCoordinates(" + id + "): could not find view");
3207 return null;
3208 }
Feng Cao023b84c2018-12-14 15:51:17 -08003209 final Rect windowVisibleDisplayFrame = new Rect();
3210 view.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
Felipe Leme284ad1c2018-11-15 18:16:12 -08003211 final int[] location = new int[2];
3212 view.getLocationOnScreen(location);
Feng Cao023b84c2018-12-14 15:51:17 -08003213 final Rect rect = new Rect(location[0], location[1] - windowVisibleDisplayFrame.top,
3214 location[0] + view.getWidth(),
3215 location[1] - windowVisibleDisplayFrame.top + view.getHeight());
Felipe Leme284ad1c2018-11-15 18:16:12 -08003216 if (sVerbose) {
3217 Log.v(TAG, "Coordinates for " + id + ": " + rect);
3218 }
3219 return rect;
3220 }
3221
3222 @Override
3223 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
3224 final AutofillManager afm = mAfm.get();
3225 if (afm != null) {
3226 afm.post(() -> afm.autofill(sessionId, ids, values));
3227 }
3228 }
Feng Cao158b6562019-01-07 15:42:52 -08003229
3230 @Override
3231 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
3232 Rect anchorBounds, IAutofillWindowPresenter presenter) {
3233 final AutofillManager afm = mAfm.get();
3234 if (afm != null) {
3235 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
3236 presenter));
3237 }
3238 }
3239
3240 @Override
3241 public void requestHideFillUi(int sessionId, AutofillId id) {
3242 final AutofillManager afm = mAfm.get();
3243 if (afm != null) {
3244 afm.post(() -> afm.requestHideFillUi(id, false));
3245 }
3246 }
Svet Ganov782043c2017-02-11 00:52:02 +00003247 }
Felipe Leme3461d3c2017-01-19 08:54:55 -08003248}