blob: e047ed203c5bdfe92968c614907e60413bf7ff29 [file] [log] [blame]
Felipe Leme3461d3c2017-01-19 08:54:55 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.autofill;
18
Felipe Lemed633f072017-02-14 10:17:17 -080019import static android.view.autofill.Helper.DEBUG;
Felipe Leme0ab53dc2017-02-23 08:33:18 -080020import static android.view.autofill.Helper.VERBOSE;
Felipe Lemed633f072017-02-14 10:17:17 -080021
Felipe Lemee6010f22017-03-03 11:19:51 -080022import android.annotation.IntDef;
Philip P. Moltmann44611812017-02-23 12:52:46 -080023import android.annotation.NonNull;
Felipe Lemee6010f22017-03-03 11:19:51 -080024import android.annotation.Nullable;
Felipe Leme3461d3c2017-01-19 08:54:55 -080025import android.content.Context;
Svet Ganov782043c2017-02-11 00:52:02 +000026import android.content.Intent;
27import android.content.IntentSender;
Felipe Leme3461d3c2017-01-19 08:54:55 -080028import android.graphics.Rect;
Felipe Leme4753bb02017-03-22 20:24:00 -070029import android.metrics.LogMaker;
Svet Ganov782043c2017-02-11 00:52:02 +000030import android.os.Bundle;
Svet Ganov28a2c7e2017-02-21 10:16:16 -080031import android.os.IBinder;
Svet Ganov782043c2017-02-11 00:52:02 +000032import android.os.Parcelable;
Felipe Leme3461d3c2017-01-19 08:54:55 -080033import android.os.RemoteException;
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -070034import android.service.autofill.AutofillService;
35import android.service.autofill.FillEventHistory;
Felipe Leme4753bb02017-03-22 20:24:00 -070036import android.util.ArrayMap;
Philip P. Moltmann494c3f52017-04-11 10:13:33 -070037import android.util.ArraySet;
Felipe Leme3461d3c2017-01-19 08:54:55 -080038import android.util.Log;
Felipe Leme4753bb02017-03-22 20:24:00 -070039import android.util.SparseArray;
Felipe Leme3461d3c2017-01-19 08:54:55 -080040import android.view.View;
Felipe Lemee6010f22017-03-03 11:19:51 -080041import android.view.WindowManagerGlobal;
Felipe Leme3461d3c2017-01-19 08:54:55 -080042
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070043import com.android.internal.annotations.GuardedBy;
Felipe Leme4753bb02017-03-22 20:24:00 -070044import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.nano.MetricsProto;
46
Felipe Lemee6010f22017-03-03 11:19:51 -080047import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
Svet Ganov782043c2017-02-11 00:52:02 +000049import java.lang.ref.WeakReference;
50import java.util.List;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -070051import java.util.Objects;
Svet Ganov782043c2017-02-11 00:52:02 +000052
Felipe Leme3461d3c2017-01-19 08:54:55 -080053/**
54 * App entry point to the AutoFill Framework.
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070055 *
56 * <p>It is safe to call into this from any thread.
Felipe Leme3461d3c2017-01-19 08:54:55 -080057 */
58// TODO(b/33197203): improve this javadoc
Felipe Lemebab851c2017-02-03 18:45:08 -080059//TODO(b/33197203): restrict manager calls to activity
Felipe Leme640f30a2017-03-06 15:44:06 -080060public final class AutofillManager {
Felipe Leme3461d3c2017-01-19 08:54:55 -080061
Felipe Leme640f30a2017-03-06 15:44:06 -080062 private static final String TAG = "AutofillManager";
Felipe Leme3461d3c2017-01-19 08:54:55 -080063
Svet Ganov782043c2017-02-11 00:52:02 +000064 /**
65 * Intent extra: The assist structure which captures the filled screen.
Felipe Leme7320ca92017-03-29 15:09:54 -070066 *
Svet Ganov782043c2017-02-11 00:52:02 +000067 * <p>
68 * Type: {@link android.app.assist.AssistStructure}
Svet Ganov782043c2017-02-11 00:52:02 +000069 */
70 public static final String EXTRA_ASSIST_STRUCTURE =
71 "android.view.autofill.extra.ASSIST_STRUCTURE";
72
73 /**
74 * Intent extra: The result of an authentication operation. It is
75 * either a fully populated {@link android.service.autofill.FillResponse}
76 * or a fully populated {@link android.service.autofill.Dataset} if
77 * a response or a dataset is being authenticated respectively.
78 *
79 * <p>
80 * Type: {@link android.service.autofill.FillResponse} or a
81 * {@link android.service.autofill.Dataset}
Svet Ganov782043c2017-02-11 00:52:02 +000082 */
83 public static final String EXTRA_AUTHENTICATION_RESULT =
84 "android.view.autofill.extra.AUTHENTICATION_RESULT";
85
Felipe Leme7320ca92017-03-29 15:09:54 -070086 /**
87 * Intent extra: The optional extras provided by the
88 * {@link android.service.autofill.AutofillService}.
89 *
90 * <p>For example, when the service responds to a {@link
91 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
92 * a {@code FillResponse} that requires authentication, the Intent that launches the
93 * service authentication will contain the Bundle set by
Felipe Leme49e96962017-04-20 15:46:18 -070094 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
Felipe Leme7320ca92017-03-29 15:09:54 -070095 *
96 * <p>
97 * Type: {@link android.os.Bundle}
98 */
99 public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS";
100
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700101 static final String SESSION_ID_TAG = "android:sessionId";
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700102 static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
103
Felipe Leme2ac463e2017-03-13 14:06:25 -0700104 // Public flags start from the lowest bit
105 /**
106 * Indicates autofill was explicitly requested by the user.
Svet Ganov013efe12017-04-13 21:56:16 -0700107 *
108 * @deprecated Use {@link android.service.autofill.FillRequest#FLAG_MANUAL_REQUEST}
Felipe Leme2ac463e2017-03-13 14:06:25 -0700109 */
Felipe Leme49e96962017-04-20 15:46:18 -0700110 // TODO(b/33197203): remove (and change value of private flags)
Svet Ganov013efe12017-04-13 21:56:16 -0700111 @Deprecated
Felipe Leme2ac463e2017-03-13 14:06:25 -0700112 public static final int FLAG_MANUAL_REQUEST = 0x1;
113
114 // Private flags start from the highest bit
115 /** @hide */ public static final int FLAG_START_SESSION = 0x80000000;
116 /** @hide */ public static final int FLAG_VIEW_ENTERED = 0x40000000;
117 /** @hide */ public static final int FLAG_VIEW_EXITED = 0x20000000;
118 /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800119
Felipe Leme4753bb02017-03-22 20:24:00 -0700120 private final MetricsLogger mMetricsLogger = new MetricsLogger();
Svet Ganov782043c2017-02-11 00:52:02 +0000121
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700122 /**
123 * There is currently no session running.
124 * {@hide}
125 */
126 public static final int NO_SESSION = Integer.MIN_VALUE;
127
Svet Ganov782043c2017-02-11 00:52:02 +0000128 private final IAutoFillManager mService;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700129
130 private final Object mLock = new Object();
131
132 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000133 private IAutoFillManagerClient mServiceClient;
134
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700135 @GuardedBy("mLock")
Felipe Lemee6010f22017-03-03 11:19:51 -0800136 private AutofillCallback mCallback;
137
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700138 private final Context mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000139
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700140 @GuardedBy("mLock")
141 private int mSessionId = NO_SESSION;
142
143 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000144 private boolean mEnabled;
145
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700146 /** If a view changes to this mapping the autofill operation was successful */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700147 @GuardedBy("mLock")
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700148 @Nullable private ParcelableMap mLastAutofilledData;
149
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700150 /** If view tracking is enabled, contains the tracking state */
151 @GuardedBy("mLock")
152 @Nullable private TrackedViews mTrackedViews;
153
Svet Ganov782043c2017-02-11 00:52:02 +0000154 /** @hide */
Felipe Leme640f30a2017-03-06 15:44:06 -0800155 public interface AutofillClient {
Svet Ganov782043c2017-02-11 00:52:02 +0000156 /**
Svet Ganov782043c2017-02-11 00:52:02 +0000157 * Asks the client to start an authentication flow.
158 *
159 * @param intent The authentication intent.
160 * @param fillInIntent The authentication fill-in intent.
161 */
Felipe Leme4753bb02017-03-22 20:24:00 -0700162 void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent);
Svet Ganov17db9dc2017-02-21 19:54:31 -0800163
164 /**
165 * Tells the client this manager has state to be reset.
166 */
Felipe Leme4753bb02017-03-22 20:24:00 -0700167 void autofillCallbackResetableStateAvailable();
168
169 /**
170 * Request showing the autofill UI.
171 *
172 * @param anchor The real view the UI needs to anchor to.
173 * @param width The width of the fill UI content.
174 * @param height The height of the fill UI content.
175 * @param virtualBounds The bounds of the virtual decendant of the anchor.
176 * @param presenter The presenter that controls the fill UI window.
177 * @return Whether the UI was shown.
178 */
179 boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
180 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
181
182 /**
183 * Request hiding the autofill UI.
184 *
185 * @return Whether the UI was hidden.
186 */
187 boolean autofillCallbackRequestHideFillUi();
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700188
189 /**
190 * Checks if the view is currently attached and visible.
191 *
192 * @return {@code true} iff the view is attached or visible
193 */
194 boolean getViewVisibility(int viewId);
195
196 /**
197 * Checks is the client is currently visible as understood by autofill.
198 *
199 * @return {@code true} if the client is currently visible
200 */
201 boolean isVisibleForAutofill();
Svet Ganov782043c2017-02-11 00:52:02 +0000202 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800203
204 /**
205 * @hide
206 */
Felipe Leme640f30a2017-03-06 15:44:06 -0800207 public AutofillManager(Context context, IAutoFillManager service) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800208 mContext = context;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800209 mService = service;
210 }
211
212 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700213 * Restore state after activity lifecycle
214 *
215 * @param savedInstanceState The state to be restored
216 *
217 * {@hide}
218 */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700219 public void onCreate(Bundle savedInstanceState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700220 if (!hasAutofillFeature()) {
221 return;
222 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700223 synchronized (mLock) {
224 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
225
226 if (mSessionId != NO_SESSION) {
227 Log.w(TAG, "New session was started before onCreate()");
228 return;
229 }
230
231 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
232
233 if (mSessionId != NO_SESSION) {
234 ensureServiceClientAddedIfNeededLocked();
235
236 final AutofillClient client = getClientLocked();
237 if (client != null) {
238 try {
239 final boolean sessionWasRestored = mService.restoreSession(mSessionId,
240 mContext.getActivityToken(), mServiceClient.asBinder());
241
242 if (!sessionWasRestored) {
243 Log.w(TAG, "Session " + mSessionId + " could not be restored");
244 mSessionId = NO_SESSION;
245 } else {
246 if (DEBUG) {
247 Log.d(TAG, "session " + mSessionId + " was restored");
248 }
249
250 client.autofillCallbackResetableStateAvailable();
251 }
252 } catch (RemoteException e) {
253 Log.e(TAG, "Could not figure out if there was an autofill session", e);
254 }
255 }
256 }
257 }
258 }
259
260 /**
261 * Set window future popup windows should be attached to.
262 *
263 * @param windowToken The window the popup windows should be attached to
264 *
265 * {@hide}
266 */
267 public void onAttachedToWindow(@NonNull IBinder windowToken) {
Svet Ganov43574b02017-04-12 09:25:20 -0700268 if (!hasAutofillFeature()) {
269 return;
270 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700271 synchronized (mLock) {
272 if (mSessionId == NO_SESSION) {
273 return;
274 }
275
276 try {
277 mService.setWindow(mSessionId, windowToken);
278 } catch (RemoteException e) {
279 Log.e(TAG, "Could not attach window to session " + mSessionId);
280 }
281 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700282 }
283
284 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700285 * Called once the client becomes visible.
286 *
287 * @see AutofillClient#isVisibleForAutofill()
288 *
289 * {@hide}
290 */
291 public void onVisibleForAutofill() {
292 synchronized (mLock) {
293 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
294 mTrackedViews.onVisibleForAutofill();
295 }
296 }
297 }
298
299 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700300 * Save state before activity lifecycle
301 *
302 * @param outState Place to store the state
303 *
304 * {@hide}
305 */
306 public void onSaveInstanceState(Bundle outState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700307 if (!hasAutofillFeature()) {
308 return;
309 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700310 synchronized (mLock) {
311 if (mSessionId != NO_SESSION) {
312 outState.putInt(SESSION_ID_TAG, mSessionId);
313 }
314
315 if (mLastAutofilledData != null) {
316 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
317 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700318 }
319 }
320
321 /**
322 * Checks whether autofill is enabled for the current user.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700323 *
324 * <p>Typically used to determine whether the option to explicitly request autofill should
325 * be offered - see {@link #requestAutofill(View)}.
326 *
327 * @return whether autofill is enabled for the current user.
328 */
329 public boolean isEnabled() {
Svet Ganov43574b02017-04-12 09:25:20 -0700330 if (!hasAutofillFeature()) {
331 return false;
332 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700333 synchronized (mLock) {
334 ensureServiceClientAddedIfNeededLocked();
335 return mEnabled;
336 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700337 }
338
339 /**
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700340 * Should always be called from {@link AutofillService#getFillEventHistory()}.
341 *
342 * @hide
343 */
344 @Nullable public FillEventHistory getFillEventHistory() {
345 try {
346 return mService.getFillEventHistory();
347 } catch (RemoteException e) {
348 e.rethrowFromSystemServer();
349 return null;
350 }
351 }
352
353 /**
Felipe Leme2ac463e2017-03-13 14:06:25 -0700354 * Explicitly requests a new autofill context.
355 *
356 * <p>Normally, the autofill context is automatically started when autofillable views are
357 * focused, but this method should be used in the cases where it must be explicitly requested,
358 * like a view that provides a contextual menu allowing users to autofill the activity.
359 *
360 * @param view view requesting the new autofill context.
361 */
362 public void requestAutofill(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700363 if (!hasAutofillFeature()) {
364 return;
365 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700366 synchronized (mLock) {
367 ensureServiceClientAddedIfNeededLocked();
Felipe Leme2ac463e2017-03-13 14:06:25 -0700368
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700369 if (!mEnabled) {
370 return;
371 }
372
373 final AutofillId id = getAutofillId(view);
374 final AutofillValue value = view.getAutofillValue();
375
376 startSessionLocked(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700377 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700378 }
379
380 /**
381 * Explicitly requests a new autofill context for virtual views.
382 *
383 * <p>Normally, the autofill context is automatically started when autofillable views are
384 * focused, but this method should be used in the cases where it must be explicitly requested,
385 * like a virtual view that provides a contextual menu allowing users to autofill the activity.
386 *
387 * @param view the {@link View} whose descendant is the virtual view.
388 * @param childId id identifying the virtual child inside the view.
389 * @param bounds child boundaries, relative to the top window.
390 */
391 public void requestAutofill(@NonNull View view, int childId, @NonNull Rect bounds) {
Svet Ganov43574b02017-04-12 09:25:20 -0700392 if (!hasAutofillFeature()) {
393 return;
394 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700395 synchronized (mLock) {
396 ensureServiceClientAddedIfNeededLocked();
Felipe Leme2ac463e2017-03-13 14:06:25 -0700397
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700398 if (!mEnabled) {
399 return;
400 }
401
402 final AutofillId id = getAutofillId(view, childId);
403 startSessionLocked(id, view.getWindowToken(), bounds, null, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700404 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700405 }
406
407
408 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700409 * Called when a {@link View} that supports autofill is entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800410 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700411 * @param view {@link View} that was entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800412 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700413 public void notifyViewEntered(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700414 if (!hasAutofillFeature()) {
415 return;
416 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700417 AutofillCallback callback = null;
418 synchronized (mLock) {
419 ensureServiceClientAddedIfNeededLocked();
Svet Ganov782043c2017-02-11 00:52:02 +0000420
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700421 if (!mEnabled) {
422 if (mCallback != null) {
423 callback = mCallback;
424 }
425 } else {
426 final AutofillId id = getAutofillId(view);
427 final AutofillValue value = view.getAutofillValue();
428
429 if (mSessionId == NO_SESSION) {
430 // Starts new session.
431 startSessionLocked(id, view.getWindowToken(), null, value, 0);
432 } else {
433 // Update focus on existing session.
434 updateSessionLocked(id, null, value, FLAG_VIEW_ENTERED);
435 }
Felipe Leme24aae152017-03-15 12:33:01 -0700436 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800437 }
438
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700439 if (callback != null) {
440 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Svet Ganov782043c2017-02-11 00:52:02 +0000441 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800442 }
443
444 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700445 * Called when a {@link View} that supports autofill is exited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800446 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700447 * @param view {@link View} that was exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800448 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700449 public void notifyViewExited(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700450 if (!hasAutofillFeature()) {
451 return;
452 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700453 synchronized (mLock) {
454 ensureServiceClientAddedIfNeededLocked();
Philip P. Moltmann44611812017-02-23 12:52:46 -0800455
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700456 if (mEnabled && mSessionId != NO_SESSION) {
457 final AutofillId id = getAutofillId(view);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800458
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700459 // Update focus on existing session.
460 updateSessionLocked(id, null, null, FLAG_VIEW_EXITED);
461 }
Philip P. Moltmann44611812017-02-23 12:52:46 -0800462 }
463 }
464
465 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700466 * Called when a {@link View view's} visibility changes.
467 *
468 * @param view {@link View} that was exited.
469 * @param isVisible visible if the view is visible in the view hierarchy.
470 *
471 * @hide
472 */
473 public void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
474 synchronized (mLock) {
475 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
476 mTrackedViews.notifyViewVisibilityChange(view, isVisible);
477 }
478 }
479 }
480
481 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700482 * Called when a virtual view that supports autofill is entered.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800483 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700484 * @param view the {@link View} whose descendant is the virtual view.
485 * @param childId id identifying the virtual child inside the view.
Felipe Lemebab851c2017-02-03 18:45:08 -0800486 * @param bounds child boundaries, relative to the top window.
Felipe Lemebab851c2017-02-03 18:45:08 -0800487 */
Felipe Leme81f01d92017-03-16 17:13:25 -0700488 public void notifyViewEntered(@NonNull View view, int childId, @NonNull Rect bounds) {
Svet Ganov43574b02017-04-12 09:25:20 -0700489 if (!hasAutofillFeature()) {
490 return;
491 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700492 AutofillCallback callback = null;
493 synchronized (mLock) {
494 ensureServiceClientAddedIfNeededLocked();
Svet Ganov782043c2017-02-11 00:52:02 +0000495
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700496 if (!mEnabled) {
497 if (mCallback != null) {
498 callback = mCallback;
499 }
500 } else {
501 final AutofillId id = getAutofillId(view, childId);
502
503 if (mSessionId == NO_SESSION) {
504 // Starts new session.
505 startSessionLocked(id, view.getWindowToken(), bounds, null, 0);
506 } else {
507 // Update focus on existing session.
508 updateSessionLocked(id, bounds, null, FLAG_VIEW_ENTERED);
509 }
Felipe Leme24aae152017-03-15 12:33:01 -0700510 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800511 }
512
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700513 if (callback != null) {
514 callback.onAutofillEvent(view, childId,
515 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800516 }
517 }
518
519 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700520 * Called when a virtual view that supports autofill is exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800521 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700522 * @param view the {@link View} whose descendant is the virtual view.
523 * @param childId id identifying the virtual child inside the view.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800524 */
Felipe Leme81f01d92017-03-16 17:13:25 -0700525 public void notifyViewExited(@NonNull View view, int childId) {
Svet Ganov43574b02017-04-12 09:25:20 -0700526 if (!hasAutofillFeature()) {
527 return;
528 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700529 synchronized (mLock) {
530 ensureServiceClientAddedIfNeededLocked();
Philip P. Moltmann44611812017-02-23 12:52:46 -0800531
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700532 if (mEnabled && mSessionId != NO_SESSION) {
533 final AutofillId id = getAutofillId(view, childId);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800534
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700535 // Update focus on existing session.
536 updateSessionLocked(id, null, null, FLAG_VIEW_EXITED);
537 }
Svet Ganov782043c2017-02-11 00:52:02 +0000538 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800539 }
540
541 /**
Felipe Leme640f30a2017-03-06 15:44:06 -0800542 * Called to indicate the value of an autofillable {@link View} changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800543 *
Philip P. Moltmann44611812017-02-23 12:52:46 -0800544 * @param view view whose value changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800545 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700546 public void notifyValueChanged(View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700547 if (!hasAutofillFeature()) {
548 return;
549 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700550 AutofillId id = null;
551 boolean valueWasRead = false;
552 AutofillValue value = null;
553
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700554 synchronized (mLock) {
555 // If the session is gone some fields might still be highlighted, hence we have to
556 // remove the isAutofilled property even if no sessions are active.
557 if (mLastAutofilledData == null) {
558 view.setAutofilled(false);
559 } else {
560 id = getAutofillId(view);
561 if (mLastAutofilledData.containsKey(id)) {
562 value = view.getAutofillValue();
563 valueWasRead = true;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700564
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700565 if (Objects.equals(mLastAutofilledData.get(id), value)) {
566 view.setAutofilled(true);
567 } else {
568 view.setAutofilled(false);
569 mLastAutofilledData.remove(id);
570 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700571 } else {
572 view.setAutofilled(false);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700573 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700574 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700575
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700576 if (!mEnabled || mSessionId == NO_SESSION) {
577 return;
578 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800579
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700580 if (id == null) {
581 id = getAutofillId(view);
582 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700583
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700584 if (!valueWasRead) {
585 value = view.getAutofillValue();
586 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700587
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700588 updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED);
589 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800590 }
591
Felipe Lemebab851c2017-02-03 18:45:08 -0800592 /**
Felipe Leme640f30a2017-03-06 15:44:06 -0800593 * Called to indicate the value of an autofillable virtual {@link View} changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800594 *
Felipe Leme2ac463e2017-03-13 14:06:25 -0700595 * @param view the {@link View} whose descendant is the virtual view.
Felipe Lemebab851c2017-02-03 18:45:08 -0800596 * @param childId id identifying the virtual child inside the parent view.
597 * @param value new value of the child.
598 */
Felipe Leme81f01d92017-03-16 17:13:25 -0700599 public void notifyValueChanged(View view, int childId, AutofillValue value) {
Svet Ganov43574b02017-04-12 09:25:20 -0700600 if (!hasAutofillFeature()) {
601 return;
602 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700603 synchronized (mLock) {
604 if (!mEnabled || mSessionId == NO_SESSION) {
605 return;
606 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800607
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700608 final AutofillId id = getAutofillId(view, childId);
609 updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED);
610 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800611 }
612
613 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700614 * Called to indicate the current autofill context should be commited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800615 *
616 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
617 * call this method after the form is submitted and another page is rendered.
618 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700619 public void commit() {
Svet Ganov43574b02017-04-12 09:25:20 -0700620 if (!hasAutofillFeature()) {
621 return;
622 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700623 synchronized (mLock) {
624 if (!mEnabled && mSessionId == NO_SESSION) {
625 return;
626 }
Svet Ganov782043c2017-02-11 00:52:02 +0000627
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700628 finishSessionLocked();
629 }
Felipe Leme0200d9e2017-01-24 15:10:26 -0800630 }
631
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700632 /**
633 * Called to indicate the current autofill context should be cancelled.
634 *
635 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
636 * call this method if the user does not post the form but moves to another form in this page.
637 */
638 public void cancel() {
Svet Ganov43574b02017-04-12 09:25:20 -0700639 if (!hasAutofillFeature()) {
640 return;
641 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700642 synchronized (mLock) {
643 if (!mEnabled && mSessionId == NO_SESSION) {
644 return;
645 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700646
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700647 cancelSessionLocked();
648 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700649 }
650
Svet Ganovf20a0372017-04-10 17:08:05 -0700651 /**
652 * If the app calling this API has enabled autofill services they
653 * will be disabled.
654 */
655 public void disableOwnedAutofillServices() {
Svet Ganov43574b02017-04-12 09:25:20 -0700656 if (!hasAutofillFeature()) {
657 return;
658 }
Svet Ganovf20a0372017-04-10 17:08:05 -0700659 try {
660 mService.disableOwnedAutofillServices(mContext.getUserId());
661 } catch (RemoteException e) {
662 throw e.rethrowFromSystemServer();
663 }
664 }
665
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700666 private AutofillClient getClientLocked() {
Felipe Leme640f30a2017-03-06 15:44:06 -0800667 if (mContext instanceof AutofillClient) {
668 return (AutofillClient) mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000669 }
Svet Ganov17db9dc2017-02-21 19:54:31 -0800670 return null;
Svet Ganov782043c2017-02-11 00:52:02 +0000671 }
672
673 /** @hide */
674 public void onAuthenticationResult(Intent data) {
Svet Ganov43574b02017-04-12 09:25:20 -0700675 if (!hasAutofillFeature()) {
676 return;
677 }
Felipe Lemed633f072017-02-14 10:17:17 -0800678 // TODO(b/33197203): the result code is being ignored, so this method is not reliably
679 // handling the cases where it's not RESULT_OK: it works fine if the service does not
680 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
681 // service set the extra and returned RESULT_CANCELED...
682
683 if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data);
684
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700685 synchronized (mLock) {
686 if (mSessionId == NO_SESSION || data == null) {
687 return;
688 }
689 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
690 final Bundle responseData = new Bundle();
691 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
692 try {
693 mService.setAuthenticationResult(responseData, mSessionId, mContext.getUserId());
694 } catch (RemoteException e) {
695 Log.e(TAG, "Error delivering authentication result", e);
696 }
Svet Ganov782043c2017-02-11 00:52:02 +0000697 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800698 }
699
Felipe Leme640f30a2017-03-06 15:44:06 -0800700 private static AutofillId getAutofillId(View view) {
701 return new AutofillId(view.getAccessibilityViewId());
Felipe Leme0200d9e2017-01-24 15:10:26 -0800702 }
703
Felipe Leme640f30a2017-03-06 15:44:06 -0800704 private static AutofillId getAutofillId(View parent, int childId) {
705 return new AutofillId(parent.getAccessibilityViewId(), childId);
Felipe Lemebab851c2017-02-03 18:45:08 -0800706 }
707
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700708 private void startSessionLocked(@NonNull AutofillId id, @NonNull IBinder windowToken,
Philip P. Moltmann7b771162017-03-03 17:22:57 -0800709 @NonNull Rect bounds, @NonNull AutofillValue value, int flags) {
Svet Ganov782043c2017-02-11 00:52:02 +0000710 if (DEBUG) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700711 Log.d(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
Felipe Leme2ac463e2017-03-13 14:06:25 -0700712 + ", flags=" + flags);
Felipe Lemebab851c2017-02-03 18:45:08 -0800713 }
Felipe Lemee6010f22017-03-03 11:19:51 -0800714
Felipe Lemebab851c2017-02-03 18:45:08 -0800715 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700716 mSessionId = mService.startSession(mContext.getActivityToken(), windowToken,
Felipe Lemee6010f22017-03-03 11:19:51 -0800717 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
Philip P. Moltmann7b771162017-03-03 17:22:57 -0800718 mCallback != null, flags, mContext.getOpPackageName());
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700719 AutofillClient client = getClientLocked();
Svet Ganov17db9dc2017-02-21 19:54:31 -0800720 if (client != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700721 client.autofillCallbackResetableStateAvailable();
Svet Ganov17db9dc2017-02-21 19:54:31 -0800722 }
Svet Ganov782043c2017-02-11 00:52:02 +0000723 } catch (RemoteException e) {
724 throw e.rethrowFromSystemServer();
725 }
726 }
727
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700728 private void finishSessionLocked() {
Svet Ganov782043c2017-02-11 00:52:02 +0000729 if (DEBUG) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700730 Log.d(TAG, "finishSessionLocked()");
Svet Ganov782043c2017-02-11 00:52:02 +0000731 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700732
Svet Ganov782043c2017-02-11 00:52:02 +0000733 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700734 mService.finishSession(mSessionId, mContext.getUserId());
Felipe Lemebab851c2017-02-03 18:45:08 -0800735 } catch (RemoteException e) {
736 throw e.rethrowFromSystemServer();
737 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700738
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700739 mTrackedViews = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700740 mSessionId = NO_SESSION;
Felipe Lemebab851c2017-02-03 18:45:08 -0800741 }
742
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700743 private void cancelSessionLocked() {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700744 if (DEBUG) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700745 Log.d(TAG, "cancelSessionLocked()");
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700746 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700747
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700748 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700749 mService.cancelSession(mSessionId, mContext.getUserId());
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700750 } catch (RemoteException e) {
751 throw e.rethrowFromSystemServer();
752 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700753
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700754 mTrackedViews = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700755 mSessionId = NO_SESSION;
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700756 }
757
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700758 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
Svet Ganov782043c2017-02-11 00:52:02 +0000759 if (DEBUG) {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700760 if (VERBOSE || (flags & FLAG_VIEW_EXITED) != 0) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700761 Log.d(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
762 + ", value=" + value + ", flags=" + flags);
Felipe Leme0ab53dc2017-02-23 08:33:18 -0800763 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800764 }
Felipe Leme0ab53dc2017-02-23 08:33:18 -0800765
Felipe Leme3461d3c2017-01-19 08:54:55 -0800766 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700767 mService.updateSession(mSessionId, id, bounds, value, flags, mContext.getUserId());
Felipe Leme3461d3c2017-01-19 08:54:55 -0800768 } catch (RemoteException e) {
769 throw e.rethrowFromSystemServer();
770 }
771 }
Svet Ganov782043c2017-02-11 00:52:02 +0000772
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700773 private void ensureServiceClientAddedIfNeededLocked() {
774 if (getClientLocked() == null) {
Svet Ganov782043c2017-02-11 00:52:02 +0000775 return;
776 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700777
Svet Ganov782043c2017-02-11 00:52:02 +0000778 if (mServiceClient == null) {
Felipe Leme640f30a2017-03-06 15:44:06 -0800779 mServiceClient = new AutofillManagerClient(this);
Svet Ganov782043c2017-02-11 00:52:02 +0000780 try {
781 mEnabled = mService.addClient(mServiceClient, mContext.getUserId());
782 } catch (RemoteException e) {
783 throw e.rethrowFromSystemServer();
784 }
785 }
786 }
787
Felipe Lemee6010f22017-03-03 11:19:51 -0800788 /**
789 * Registers a {@link AutofillCallback} to receive autofill events.
790 *
791 * @param callback callback to receive events.
792 */
793 public void registerCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -0700794 if (!hasAutofillFeature()) {
795 return;
796 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700797 synchronized (mLock) {
798 if (callback == null) return;
Felipe Lemee6010f22017-03-03 11:19:51 -0800799
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700800 final boolean hadCallback = mCallback != null;
801 mCallback = callback;
Felipe Lemee6010f22017-03-03 11:19:51 -0800802
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700803 if (!hadCallback) {
804 try {
805 mService.setHasCallback(mSessionId, mContext.getUserId(), true);
806 } catch (RemoteException e) {
807 throw e.rethrowFromSystemServer();
808 }
Felipe Lemee6010f22017-03-03 11:19:51 -0800809 }
810 }
811 }
812
813 /**
814 * Unregisters a {@link AutofillCallback} to receive autofill events.
815 *
816 * @param callback callback to stop receiving events.
817 */
818 public void unregisterCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -0700819 if (!hasAutofillFeature()) {
820 return;
821 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700822 synchronized (mLock) {
823 if (callback == null || mCallback == null || callback != mCallback) return;
Felipe Lemee6010f22017-03-03 11:19:51 -0800824
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700825 mCallback = null;
Felipe Lemee6010f22017-03-03 11:19:51 -0800826
Felipe Lemee6010f22017-03-03 11:19:51 -0800827 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700828 mService.setHasCallback(mSessionId, mContext.getUserId(), false);
Felipe Lemee6010f22017-03-03 11:19:51 -0800829 } catch (RemoteException e) {
830 throw e.rethrowFromSystemServer();
831 }
832 }
833 }
834
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700835 private void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id, int width,
836 int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700837 final View anchor = findAchorView(windowToken, id);
838 if (anchor == null) {
839 return;
840 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700841
842 AutofillCallback callback = null;
843 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700844 if (mSessionId == sessionId) {
845 AutofillClient client = getClientLocked();
846
847 if (client != null) {
848 if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
849 anchorBounds, presenter) && mCallback != null) {
850 callback = mCallback;
851 }
852 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700853 }
854 }
855
856 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700857 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700858 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -0700859 AutofillCallback.EVENT_INPUT_SHOWN);
860 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700861 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
Felipe Leme4753bb02017-03-22 20:24:00 -0700862 }
863 }
864 }
865
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700866 private void authenticate(int sessionId, IntentSender intent, Intent fillInIntent) {
867 synchronized (mLock) {
868 if (sessionId == mSessionId) {
869 AutofillClient client = getClientLocked();
870 if (client != null) {
871 client.autofillCallbackAuthenticate(intent, fillInIntent);
872 }
873 }
874 }
875 }
876
877 private void setState(boolean enabled) {
878 synchronized (mLock) {
879 mEnabled = enabled;
880 }
881 }
882
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700883 /**
884 * Sets a view as autofilled if the current value is the {code targetValue}.
885 *
886 * @param view The view that is to be autofilled
887 * @param targetValue The value we want to fill into view
888 */
889 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
890 AutofillValue currentValue = view.getAutofillValue();
891 if (Objects.equals(currentValue, targetValue)) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700892 synchronized (mLock) {
893 if (mLastAutofilledData == null) {
894 mLastAutofilledData = new ParcelableMap(1);
895 }
896 mLastAutofilledData.put(getAutofillId(view), targetValue);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700897 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700898 view.setAutofilled(true);
899 }
900 }
901
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700902 private void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
Felipe Leme4753bb02017-03-22 20:24:00 -0700903 List<AutofillValue> values) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700904 synchronized (mLock) {
905 if (sessionId != mSessionId) {
906 return;
Felipe Leme4753bb02017-03-22 20:24:00 -0700907 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700908
909 final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
910 if (root == null) {
911 return;
912 }
913
914 final int itemCount = ids.size();
915 int numApplied = 0;
916 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
917
918 for (int i = 0; i < itemCount; i++) {
919 final AutofillId id = ids.get(i);
920 final AutofillValue value = values.get(i);
921 final int viewId = id.getViewId();
922 final View view = root.findViewByAccessibilityIdTraversal(viewId);
923 if (view == null) {
924 Log.w(TAG, "autofill(): no View with id " + viewId);
925 continue;
Felipe Leme4753bb02017-03-22 20:24:00 -0700926 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700927 if (id.isVirtual()) {
928 if (virtualValues == null) {
929 // Most likely there will be just one view with virtual children.
930 virtualValues = new ArrayMap<>(1);
931 }
932 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
933 if (valuesByParent == null) {
934 // We don't know the size yet, but usually it will be just a few fields...
935 valuesByParent = new SparseArray<>(5);
936 virtualValues.put(view, valuesByParent);
937 }
938 valuesByParent.put(id.getVirtualChildId(), value);
939 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700940 // Mark the view as to be autofilled with 'value'
941 if (mLastAutofilledData == null) {
942 mLastAutofilledData = new ParcelableMap(itemCount - i);
943 }
944 mLastAutofilledData.put(id, value);
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700945
946 view.autofill(value);
947
948 // Set as autofilled if the values match now, e.g. when the value was updated
949 // synchronously.
950 // If autofill happens async, the view is set to autofilled in
951 // notifyValueChanged.
952 setAutofilledIfValuesIs(view, value);
953
954 numApplied++;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700955 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700956 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700957
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700958 if (virtualValues != null) {
959 for (int i = 0; i < virtualValues.size(); i++) {
960 final View parent = virtualValues.keyAt(i);
961 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
962 parent.autofill(childrenValues);
963 numApplied += childrenValues.size();
964 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700965 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700966
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700967 final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
968 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
969 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
970 numApplied);
971 mMetricsLogger.write(log);
972 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700973 }
974
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700975 /**
976 * Set the tracked views.
977 *
978 * @param trackedIds The views to be tracked
979 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
980 */
981 private void setTrackedViews(int sessionId, List<AutofillId> trackedIds,
982 boolean saveOnAllViewsInvisible) {
983 synchronized (mLock) {
984 if (mEnabled && mSessionId == sessionId) {
985 if (saveOnAllViewsInvisible) {
986 mTrackedViews = new TrackedViews(trackedIds);
987 } else {
988 mTrackedViews = null;
989 }
990 }
991 }
992 }
993
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700994 private void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700995 final View anchor = findAchorView(windowToken, id);
996
997 AutofillCallback callback = null;
998 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700999 if (mSessionId == sessionId) {
1000 AutofillClient client = getClientLocked();
1001
1002 if (client != null) {
1003 if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
1004 callback = mCallback;
1005 }
1006 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001007 }
1008 }
1009
1010 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001011 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001012 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001013 AutofillCallback.EVENT_INPUT_HIDDEN);
1014 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001015 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
Felipe Leme4753bb02017-03-22 20:24:00 -07001016 }
1017 }
1018 }
1019
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001020 private void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001021 final View anchor = findAchorView(windowToken, id);
1022
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001023 AutofillCallback callback = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001024 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001025 if (mSessionId == sessionId && getClientLocked() != null) {
1026 callback = mCallback;
1027 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001028 }
1029
1030 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001031 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001032 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001033 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1034 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001035 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Felipe Leme4753bb02017-03-22 20:24:00 -07001036 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001037
Felipe Leme4753bb02017-03-22 20:24:00 -07001038 }
1039 }
1040
1041 private View findAchorView(IBinder windowToken, AutofillId id) {
Felipe Lemee6010f22017-03-03 11:19:51 -08001042 final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
1043 if (root == null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001044 Log.w(TAG, "no window with token " + windowToken);
1045 return null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001046 }
1047 final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
1048 if (view == null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001049 Log.w(TAG, "no view with id " + id);
1050 return null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001051 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001052 return view;
Felipe Lemee6010f22017-03-03 11:19:51 -08001053 }
1054
Svet Ganov43574b02017-04-12 09:25:20 -07001055 private boolean hasAutofillFeature() {
1056 return mService != null;
1057 }
1058
Felipe Lemee6010f22017-03-03 11:19:51 -08001059 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001060 * View tracking information. Once all tracked views become invisible the session is finished.
1061 */
1062 private class TrackedViews {
1063 /** Visible tracked views */
1064 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
1065
1066 /** Invisible tracked views */
1067 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
1068
1069 /**
1070 * Check if set is null or value is in set.
1071 *
1072 * @param set The set or null (== empty set)
1073 * @param value The value that might be in the set
1074 *
1075 * @return {@code true} iff set is not empty and value is in set
1076 */
1077 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
1078 return set != null && set.contains(value);
1079 }
1080
1081 /**
1082 * Add a value to a set. If set is null, create a new set.
1083 *
1084 * @param set The set or null (== empty set)
1085 * @param valueToAdd The value to add
1086 *
1087 * @return The set including the new value. If set was {@code null}, a set containing only
1088 * the new value.
1089 */
1090 @NonNull
1091 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
1092 if (set == null) {
1093 set = new ArraySet<>(1);
1094 }
1095
1096 set.add(valueToAdd);
1097
1098 return set;
1099 }
1100
1101 /**
1102 * Remove a value from a set.
1103 *
1104 * @param set The set or null (== empty set)
1105 * @param valueToRemove The value to remove
1106 *
1107 * @return The set without the removed value. {@code null} if set was null, or is empty
1108 * after removal.
1109 */
1110 @Nullable
1111 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
1112 if (set == null) {
1113 return null;
1114 }
1115
1116 set.remove(valueToRemove);
1117
1118 if (set.isEmpty()) {
1119 return null;
1120 }
1121
1122 return set;
1123 }
1124
1125 /**
1126 * Set the tracked views.
1127 *
1128 * @param trackedIds The views to be tracked
1129 */
1130 TrackedViews(@NonNull List<AutofillId> trackedIds) {
1131 mVisibleTrackedIds = null;
1132 mInvisibleTrackedIds = null;
1133
1134 AutofillClient client = getClientLocked();
1135 if (trackedIds != null) {
1136 int numIds = trackedIds.size();
1137 for (int i = 0; i < numIds; i++) {
1138 AutofillId id = trackedIds.get(i);
1139
1140 boolean isVisible = true;
1141 if (client != null && client.isVisibleForAutofill()) {
1142 isVisible = client.getViewVisibility(id.getViewId());
1143 }
1144
1145 if (isVisible) {
1146 mVisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1147 } else {
1148 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1149 }
1150 }
1151 }
1152
1153 if (DEBUG) {
1154 Log.d(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
1155 + " mVisibleTrackedIds=" + mVisibleTrackedIds
1156 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
1157 }
1158
1159 if (mVisibleTrackedIds == null) {
1160 finishSessionLocked();
1161 }
1162 }
1163
1164 /**
1165 * Called when a {@link View view's} visibility changes.
1166 *
1167 * @param view {@link View} that was exited.
1168 * @param isVisible visible if the view is visible in the view hierarchy.
1169 */
1170 void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
1171 AutofillId id = getAutofillId(view);
1172 AutofillClient client = getClientLocked();
1173
1174 if (DEBUG) {
1175 Log.d(TAG, "notifyViewVisibilityChange(): id=" + id + " isVisible="
1176 + isVisible);
1177 }
1178
1179 if (client != null && client.isVisibleForAutofill()) {
1180 if (isVisible) {
1181 if (isInSet(mInvisibleTrackedIds, id)) {
1182 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
1183 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1184 }
1185 } else {
1186 if (isInSet(mVisibleTrackedIds, id)) {
1187 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
1188 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1189 }
1190 }
1191 }
1192
1193 if (mVisibleTrackedIds == null) {
1194 finishSessionLocked();
1195 }
1196 }
1197
1198 /**
1199 * Called once the client becomes visible.
1200 *
1201 * @see AutofillClient#isVisibleForAutofill()
1202 */
1203 void onVisibleForAutofill() {
1204 // The visibility of the views might have changed while the client was not started,
1205 // hence update the visibility state for all views.
1206 AutofillClient client = getClientLocked();
1207 ArraySet<AutofillId> updatedVisibleTrackedIds = null;
1208 ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
1209 if (client != null) {
1210 if (mInvisibleTrackedIds != null) {
1211 for (AutofillId id : mInvisibleTrackedIds) {
1212 if (client.getViewVisibility(id.getViewId())) {
1213 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1214
1215 if (DEBUG) {
1216 Log.i(TAG, "onVisibleForAutofill() " + id + " became visible");
1217 }
1218 } else {
1219 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1220 }
1221 }
1222 }
1223
1224 if (mVisibleTrackedIds != null) {
1225 for (AutofillId id : mVisibleTrackedIds) {
1226 if (client.getViewVisibility(id.getViewId())) {
1227 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1228 } else {
1229 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1230
1231 if (DEBUG) {
1232 Log.i(TAG, "onVisibleForAutofill() " + id + " became invisible");
1233 }
1234 }
1235 }
1236 }
1237
1238 mInvisibleTrackedIds = updatedInvisibleTrackedIds;
1239 mVisibleTrackedIds = updatedVisibleTrackedIds;
1240 }
1241
1242 if (mVisibleTrackedIds == null) {
1243 finishSessionLocked();
1244 }
1245 }
1246 }
1247
1248 /**
Felipe Lemee6010f22017-03-03 11:19:51 -08001249 * Callback for auto-fill related events.
1250 *
1251 * <p>Typically used for applications that display their own "auto-complete" views, so they can
1252 * enable / disable such views when the auto-fill UI affordance is shown / hidden.
1253 */
1254 public abstract static class AutofillCallback {
1255
1256 /** @hide */
1257 @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
1258 @Retention(RetentionPolicy.SOURCE)
1259 public @interface AutofillEventType {}
1260
1261 /**
1262 * The auto-fill input UI affordance associated with the view was shown.
1263 *
1264 * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
1265 * should be hidden upon receiving this event.
1266 */
1267 public static final int EVENT_INPUT_SHOWN = 1;
1268
1269 /**
1270 * The auto-fill input UI affordance associated with the view was hidden.
1271 *
1272 * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
1273 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
1274 */
1275 public static final int EVENT_INPUT_HIDDEN = 2;
1276
1277 /**
Felipe Leme24aae152017-03-15 12:33:01 -07001278 * The auto-fill input UI affordance associated with the view won't be shown because
1279 * autofill is not available.
1280 *
1281 * <p>If the view provides its own auto-complete UI affordance but was not displaying it
1282 * to avoid flickering, it could shown it upon receiving this event.
1283 */
1284 public static final int EVENT_INPUT_UNAVAILABLE = 3;
1285
1286 /**
Felipe Lemee6010f22017-03-03 11:19:51 -08001287 * Called after a change in the autofill state associated with a view.
1288 *
1289 * @param view view associated with the change.
1290 *
1291 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1292 */
Felipe Leme81f01d92017-03-16 17:13:25 -07001293 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
1294 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001295
1296 /**
1297 * Called after a change in the autofill state associated with a virtual view.
1298 *
1299 * @param view parent view associated with the change.
1300 * @param childId id identifying the virtual child inside the parent view.
1301 *
1302 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1303 */
Felipe Leme81f01d92017-03-16 17:13:25 -07001304 public void onAutofillEvent(@NonNull View view, int childId, @AutofillEventType int event) {
1305 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001306 }
1307
Felipe Leme640f30a2017-03-06 15:44:06 -08001308 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
1309 private final WeakReference<AutofillManager> mAfm;
Svet Ganov782043c2017-02-11 00:52:02 +00001310
Felipe Leme640f30a2017-03-06 15:44:06 -08001311 AutofillManagerClient(AutofillManager autofillManager) {
1312 mAfm = new WeakReference<>(autofillManager);
Svet Ganov782043c2017-02-11 00:52:02 +00001313 }
1314
1315 @Override
1316 public void setState(boolean enabled) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001317 final AutofillManager afm = mAfm.get();
1318 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001319 afm.mContext.getMainThreadHandler().post(() -> afm.setState(enabled));
Svet Ganov782043c2017-02-11 00:52:02 +00001320 }
1321 }
1322
1323 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001324 public void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
Felipe Leme4753bb02017-03-22 20:24:00 -07001325 List<AutofillValue> values) {
Svet Ganov782043c2017-02-11 00:52:02 +00001326 // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
1327 // dataset.extras to service
Felipe Leme640f30a2017-03-06 15:44:06 -08001328 final AutofillManager afm = mAfm.get();
1329 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001330 afm.mContext.getMainThreadHandler().post(
1331 () -> afm.autofill(sessionId, windowToken, ids, values));
Svet Ganov782043c2017-02-11 00:52:02 +00001332 }
1333 }
1334
1335 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001336 public void authenticate(int sessionId, IntentSender intent, Intent fillInIntent) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001337 final AutofillManager afm = mAfm.get();
1338 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001339 afm.mContext.getMainThreadHandler().post(
1340 () -> afm.authenticate(sessionId, intent, fillInIntent));
Svet Ganov782043c2017-02-11 00:52:02 +00001341 }
1342 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001343
1344 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001345 public void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id,
Felipe Leme4753bb02017-03-22 20:24:00 -07001346 int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001347 final AutofillManager afm = mAfm.get();
1348 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001349 afm.mContext.getMainThreadHandler().post(
1350 () -> afm.requestShowFillUi(sessionId, windowToken, id, width, height,
1351 anchorBounds, presenter));
Felipe Leme4753bb02017-03-22 20:24:00 -07001352 }
1353 }
1354
1355 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001356 public void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001357 final AutofillManager afm = mAfm.get();
1358 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001359 afm.mContext.getMainThreadHandler().post(
1360 () -> afm.requestHideFillUi(sessionId, windowToken, id));
Felipe Leme4753bb02017-03-22 20:24:00 -07001361 }
1362 }
1363
1364 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001365 public void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001366 final AutofillManager afm = mAfm.get();
1367 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001368 afm.mContext.getMainThreadHandler().post(
1369 () -> afm.notifyNoFillUi(sessionId, windowToken, id));
Felipe Lemee6010f22017-03-03 11:19:51 -08001370 }
1371 }
Svet Ganovc3d1c852017-04-12 22:34:28 -07001372
1373 @Override
1374 public void startIntentSender(IntentSender intentSender) {
1375 final AutofillManager afm = mAfm.get();
1376 if (afm != null) {
1377 afm.mContext.getMainThreadHandler().post(() -> {
1378 try {
1379 afm.mContext.startIntentSender(intentSender, null, 0, 0, 0);
1380 } catch (IntentSender.SendIntentException e) {
1381 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
1382 }
1383 });
1384 }
1385 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001386
1387 @Override
1388 public void setTrackedViews(int sessionId, List<AutofillId> ids,
1389 boolean saveOnAllViewsInvisible) {
1390 final AutofillManager afm = mAfm.get();
1391 if (afm != null) {
1392 afm.mContext.getMainThreadHandler().post(
1393 () -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible)
1394 );
1395 }
1396 }
Svet Ganov782043c2017-02-11 00:52:02 +00001397 }
Felipe Leme3461d3c2017-01-19 08:54:55 -08001398}