blob: b9d42f68c6d1d85c310c3a6644cce19634cbfe52 [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/**
Felipe Leme85d1c2d2017-04-21 08:56:04 -070054 * 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 */
Felipe Leme640f30a2017-03-06 15:44:06 -080058public final class AutofillManager {
Felipe Leme3461d3c2017-01-19 08:54:55 -080059
Felipe Leme640f30a2017-03-06 15:44:06 -080060 private static final String TAG = "AutofillManager";
Felipe Leme3461d3c2017-01-19 08:54:55 -080061
Svet Ganov782043c2017-02-11 00:52:02 +000062 /**
63 * Intent extra: The assist structure which captures the filled screen.
Felipe Leme7320ca92017-03-29 15:09:54 -070064 *
Svet Ganov782043c2017-02-11 00:52:02 +000065 * <p>
66 * Type: {@link android.app.assist.AssistStructure}
Svet Ganov782043c2017-02-11 00:52:02 +000067 */
68 public static final String EXTRA_ASSIST_STRUCTURE =
69 "android.view.autofill.extra.ASSIST_STRUCTURE";
70
71 /**
72 * Intent extra: The result of an authentication operation. It is
73 * either a fully populated {@link android.service.autofill.FillResponse}
74 * or a fully populated {@link android.service.autofill.Dataset} if
75 * a response or a dataset is being authenticated respectively.
76 *
77 * <p>
78 * Type: {@link android.service.autofill.FillResponse} or a
79 * {@link android.service.autofill.Dataset}
Svet Ganov782043c2017-02-11 00:52:02 +000080 */
81 public static final String EXTRA_AUTHENTICATION_RESULT =
82 "android.view.autofill.extra.AUTHENTICATION_RESULT";
83
Felipe Leme7320ca92017-03-29 15:09:54 -070084 /**
85 * Intent extra: The optional extras provided by the
86 * {@link android.service.autofill.AutofillService}.
87 *
88 * <p>For example, when the service responds to a {@link
89 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
90 * a {@code FillResponse} that requires authentication, the Intent that launches the
91 * service authentication will contain the Bundle set by
Felipe Leme49e96962017-04-20 15:46:18 -070092 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
Felipe Leme7320ca92017-03-29 15:09:54 -070093 *
94 * <p>
95 * Type: {@link android.os.Bundle}
96 */
97 public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS";
98
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -070099 static final String SESSION_ID_TAG = "android:sessionId";
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700100 static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
101
Felipe Leme2ac463e2017-03-13 14:06:25 -0700102 // Public flags start from the lowest bit
103 /**
104 * Indicates autofill was explicitly requested by the user.
Svet Ganov013efe12017-04-13 21:56:16 -0700105 *
106 * @deprecated Use {@link android.service.autofill.FillRequest#FLAG_MANUAL_REQUEST}
Felipe Leme2ac463e2017-03-13 14:06:25 -0700107 */
Felipe Leme85d1c2d2017-04-21 08:56:04 -0700108 // TODO(b/37563972): remove (and change value of private flags)
Svet Ganov013efe12017-04-13 21:56:16 -0700109 @Deprecated
Felipe Leme2ac463e2017-03-13 14:06:25 -0700110 public static final int FLAG_MANUAL_REQUEST = 0x1;
111
112 // Private flags start from the highest bit
113 /** @hide */ public static final int FLAG_START_SESSION = 0x80000000;
114 /** @hide */ public static final int FLAG_VIEW_ENTERED = 0x40000000;
115 /** @hide */ public static final int FLAG_VIEW_EXITED = 0x20000000;
116 /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800117
Felipe Leme4753bb02017-03-22 20:24:00 -0700118 private final MetricsLogger mMetricsLogger = new MetricsLogger();
Svet Ganov782043c2017-02-11 00:52:02 +0000119
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700120 /**
121 * There is currently no session running.
122 * {@hide}
123 */
124 public static final int NO_SESSION = Integer.MIN_VALUE;
125
Svet Ganov782043c2017-02-11 00:52:02 +0000126 private final IAutoFillManager mService;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700127
128 private final Object mLock = new Object();
129
130 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000131 private IAutoFillManagerClient mServiceClient;
132
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700133 @GuardedBy("mLock")
Felipe Lemee6010f22017-03-03 11:19:51 -0800134 private AutofillCallback mCallback;
135
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700136 private final Context mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000137
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700138 @GuardedBy("mLock")
139 private int mSessionId = NO_SESSION;
140
141 @GuardedBy("mLock")
Svet Ganov782043c2017-02-11 00:52:02 +0000142 private boolean mEnabled;
143
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700144 /** If a view changes to this mapping the autofill operation was successful */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700145 @GuardedBy("mLock")
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700146 @Nullable private ParcelableMap mLastAutofilledData;
147
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700148 /** If view tracking is enabled, contains the tracking state */
149 @GuardedBy("mLock")
150 @Nullable private TrackedViews mTrackedViews;
151
Svet Ganov782043c2017-02-11 00:52:02 +0000152 /** @hide */
Felipe Leme640f30a2017-03-06 15:44:06 -0800153 public interface AutofillClient {
Svet Ganov782043c2017-02-11 00:52:02 +0000154 /**
Svet Ganov782043c2017-02-11 00:52:02 +0000155 * Asks the client to start an authentication flow.
156 *
157 * @param intent The authentication intent.
158 * @param fillInIntent The authentication fill-in intent.
159 */
Felipe Leme4753bb02017-03-22 20:24:00 -0700160 void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent);
Svet Ganov17db9dc2017-02-21 19:54:31 -0800161
162 /**
163 * Tells the client this manager has state to be reset.
164 */
Felipe Leme4753bb02017-03-22 20:24:00 -0700165 void autofillCallbackResetableStateAvailable();
166
167 /**
168 * Request showing the autofill UI.
169 *
170 * @param anchor The real view the UI needs to anchor to.
171 * @param width The width of the fill UI content.
172 * @param height The height of the fill UI content.
173 * @param virtualBounds The bounds of the virtual decendant of the anchor.
174 * @param presenter The presenter that controls the fill UI window.
175 * @return Whether the UI was shown.
176 */
177 boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
178 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
179
180 /**
181 * Request hiding the autofill UI.
182 *
183 * @return Whether the UI was hidden.
184 */
185 boolean autofillCallbackRequestHideFillUi();
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700186
187 /**
188 * Checks if the view is currently attached and visible.
189 *
190 * @return {@code true} iff the view is attached or visible
191 */
192 boolean getViewVisibility(int viewId);
193
194 /**
195 * Checks is the client is currently visible as understood by autofill.
196 *
197 * @return {@code true} if the client is currently visible
198 */
199 boolean isVisibleForAutofill();
Svet Ganov782043c2017-02-11 00:52:02 +0000200 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800201
202 /**
203 * @hide
204 */
Felipe Leme640f30a2017-03-06 15:44:06 -0800205 public AutofillManager(Context context, IAutoFillManager service) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800206 mContext = context;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800207 mService = service;
208 }
209
210 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700211 * Restore state after activity lifecycle
212 *
213 * @param savedInstanceState The state to be restored
214 *
215 * {@hide}
216 */
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700217 public void onCreate(Bundle savedInstanceState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700218 if (!hasAutofillFeature()) {
219 return;
220 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700221 synchronized (mLock) {
222 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
223
224 if (mSessionId != NO_SESSION) {
225 Log.w(TAG, "New session was started before onCreate()");
226 return;
227 }
228
229 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
230
231 if (mSessionId != NO_SESSION) {
232 ensureServiceClientAddedIfNeededLocked();
233
234 final AutofillClient client = getClientLocked();
235 if (client != null) {
236 try {
237 final boolean sessionWasRestored = mService.restoreSession(mSessionId,
238 mContext.getActivityToken(), mServiceClient.asBinder());
239
240 if (!sessionWasRestored) {
241 Log.w(TAG, "Session " + mSessionId + " could not be restored");
242 mSessionId = NO_SESSION;
243 } else {
244 if (DEBUG) {
245 Log.d(TAG, "session " + mSessionId + " was restored");
246 }
247
248 client.autofillCallbackResetableStateAvailable();
249 }
250 } catch (RemoteException e) {
251 Log.e(TAG, "Could not figure out if there was an autofill session", e);
252 }
253 }
254 }
255 }
256 }
257
258 /**
259 * Set window future popup windows should be attached to.
260 *
261 * @param windowToken The window the popup windows should be attached to
262 *
263 * {@hide}
264 */
265 public void onAttachedToWindow(@NonNull IBinder windowToken) {
Svet Ganov43574b02017-04-12 09:25:20 -0700266 if (!hasAutofillFeature()) {
267 return;
268 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700269 synchronized (mLock) {
270 if (mSessionId == NO_SESSION) {
271 return;
272 }
273
274 try {
275 mService.setWindow(mSessionId, windowToken);
276 } catch (RemoteException e) {
277 Log.e(TAG, "Could not attach window to session " + mSessionId);
278 }
279 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700280 }
281
282 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700283 * Called once the client becomes visible.
284 *
285 * @see AutofillClient#isVisibleForAutofill()
286 *
287 * {@hide}
288 */
289 public void onVisibleForAutofill() {
290 synchronized (mLock) {
291 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
292 mTrackedViews.onVisibleForAutofill();
293 }
294 }
295 }
296
297 /**
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700298 * Save state before activity lifecycle
299 *
300 * @param outState Place to store the state
301 *
302 * {@hide}
303 */
304 public void onSaveInstanceState(Bundle outState) {
Svet Ganov43574b02017-04-12 09:25:20 -0700305 if (!hasAutofillFeature()) {
306 return;
307 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700308 synchronized (mLock) {
309 if (mSessionId != NO_SESSION) {
310 outState.putInt(SESSION_ID_TAG, mSessionId);
311 }
312
313 if (mLastAutofilledData != null) {
314 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
315 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700316 }
317 }
318
319 /**
320 * Checks whether autofill is enabled for the current user.
Felipe Leme2ac463e2017-03-13 14:06:25 -0700321 *
322 * <p>Typically used to determine whether the option to explicitly request autofill should
323 * be offered - see {@link #requestAutofill(View)}.
324 *
325 * @return whether autofill is enabled for the current user.
326 */
327 public boolean isEnabled() {
Svet Ganov43574b02017-04-12 09:25:20 -0700328 if (!hasAutofillFeature()) {
329 return false;
330 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700331 synchronized (mLock) {
332 ensureServiceClientAddedIfNeededLocked();
333 return mEnabled;
334 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700335 }
336
337 /**
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700338 * Should always be called from {@link AutofillService#getFillEventHistory()}.
339 *
340 * @hide
341 */
342 @Nullable public FillEventHistory getFillEventHistory() {
343 try {
344 return mService.getFillEventHistory();
345 } catch (RemoteException e) {
346 e.rethrowFromSystemServer();
347 return null;
348 }
349 }
350
351 /**
Felipe Leme2ac463e2017-03-13 14:06:25 -0700352 * Explicitly requests a new autofill context.
353 *
354 * <p>Normally, the autofill context is automatically started when autofillable views are
355 * focused, but this method should be used in the cases where it must be explicitly requested,
356 * like a view that provides a contextual menu allowing users to autofill the activity.
357 *
358 * @param view view requesting the new autofill context.
359 */
360 public void requestAutofill(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700361 if (!hasAutofillFeature()) {
362 return;
363 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700364 synchronized (mLock) {
365 ensureServiceClientAddedIfNeededLocked();
Felipe Leme2ac463e2017-03-13 14:06:25 -0700366
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700367 if (!mEnabled) {
368 return;
369 }
370
371 final AutofillId id = getAutofillId(view);
372 final AutofillValue value = view.getAutofillValue();
373
374 startSessionLocked(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700375 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700376 }
377
378 /**
379 * Explicitly requests a new autofill context for virtual views.
380 *
381 * <p>Normally, the autofill context is automatically started when autofillable views are
382 * focused, but this method should be used in the cases where it must be explicitly requested,
383 * like a virtual view that provides a contextual menu allowing users to autofill the activity.
384 *
385 * @param view the {@link View} whose descendant is the virtual view.
386 * @param childId id identifying the virtual child inside the view.
387 * @param bounds child boundaries, relative to the top window.
388 */
389 public void requestAutofill(@NonNull View view, int childId, @NonNull Rect bounds) {
Svet Ganov43574b02017-04-12 09:25:20 -0700390 if (!hasAutofillFeature()) {
391 return;
392 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700393 synchronized (mLock) {
394 ensureServiceClientAddedIfNeededLocked();
Felipe Leme2ac463e2017-03-13 14:06:25 -0700395
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700396 if (!mEnabled) {
397 return;
398 }
399
400 final AutofillId id = getAutofillId(view, childId);
401 startSessionLocked(id, view.getWindowToken(), bounds, null, FLAG_MANUAL_REQUEST);
Felipe Leme2ac463e2017-03-13 14:06:25 -0700402 }
Felipe Leme2ac463e2017-03-13 14:06:25 -0700403 }
404
405
406 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700407 * Called when a {@link View} that supports autofill is entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800408 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700409 * @param view {@link View} that was entered.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800410 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700411 public void notifyViewEntered(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700412 if (!hasAutofillFeature()) {
413 return;
414 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700415 AutofillCallback callback = null;
416 synchronized (mLock) {
417 ensureServiceClientAddedIfNeededLocked();
Svet Ganov782043c2017-02-11 00:52:02 +0000418
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700419 if (!mEnabled) {
420 if (mCallback != null) {
421 callback = mCallback;
422 }
423 } else {
424 final AutofillId id = getAutofillId(view);
425 final AutofillValue value = view.getAutofillValue();
426
427 if (mSessionId == NO_SESSION) {
428 // Starts new session.
429 startSessionLocked(id, view.getWindowToken(), null, value, 0);
430 } else {
431 // Update focus on existing session.
432 updateSessionLocked(id, null, value, FLAG_VIEW_ENTERED);
433 }
Felipe Leme24aae152017-03-15 12:33:01 -0700434 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800435 }
436
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700437 if (callback != null) {
438 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Svet Ganov782043c2017-02-11 00:52:02 +0000439 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800440 }
441
442 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700443 * Called when a {@link View} that supports autofill is exited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800444 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700445 * @param view {@link View} that was exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800446 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700447 public void notifyViewExited(@NonNull View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700448 if (!hasAutofillFeature()) {
449 return;
450 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700451 synchronized (mLock) {
452 ensureServiceClientAddedIfNeededLocked();
Philip P. Moltmann44611812017-02-23 12:52:46 -0800453
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700454 if (mEnabled && mSessionId != NO_SESSION) {
455 final AutofillId id = getAutofillId(view);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800456
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700457 // Update focus on existing session.
458 updateSessionLocked(id, null, null, FLAG_VIEW_EXITED);
459 }
Philip P. Moltmann44611812017-02-23 12:52:46 -0800460 }
461 }
462
463 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700464 * Called when a {@link View view's} visibility changes.
465 *
466 * @param view {@link View} that was exited.
467 * @param isVisible visible if the view is visible in the view hierarchy.
468 *
469 * @hide
470 */
471 public void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
472 synchronized (mLock) {
473 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
474 mTrackedViews.notifyViewVisibilityChange(view, isVisible);
475 }
476 }
477 }
478
479 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700480 * Called when a virtual view that supports autofill is entered.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800481 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700482 * @param view the {@link View} whose descendant is the virtual view.
483 * @param childId id identifying the virtual child inside the view.
Felipe Lemebab851c2017-02-03 18:45:08 -0800484 * @param bounds child boundaries, relative to the top window.
Felipe Lemebab851c2017-02-03 18:45:08 -0800485 */
Felipe Leme81f01d92017-03-16 17:13:25 -0700486 public void notifyViewEntered(@NonNull View view, int childId, @NonNull Rect bounds) {
Svet Ganov43574b02017-04-12 09:25:20 -0700487 if (!hasAutofillFeature()) {
488 return;
489 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700490 AutofillCallback callback = null;
491 synchronized (mLock) {
492 ensureServiceClientAddedIfNeededLocked();
Svet Ganov782043c2017-02-11 00:52:02 +0000493
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700494 if (!mEnabled) {
495 if (mCallback != null) {
496 callback = mCallback;
497 }
498 } else {
499 final AutofillId id = getAutofillId(view, childId);
500
501 if (mSessionId == NO_SESSION) {
502 // Starts new session.
503 startSessionLocked(id, view.getWindowToken(), bounds, null, 0);
504 } else {
505 // Update focus on existing session.
506 updateSessionLocked(id, bounds, null, FLAG_VIEW_ENTERED);
507 }
Felipe Leme24aae152017-03-15 12:33:01 -0700508 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800509 }
510
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700511 if (callback != null) {
512 callback.onAutofillEvent(view, childId,
513 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800514 }
515 }
516
517 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700518 * Called when a virtual view that supports autofill is exited.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800519 *
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700520 * @param view the {@link View} whose descendant is the virtual view.
521 * @param childId id identifying the virtual child inside the view.
Philip P. Moltmann44611812017-02-23 12:52:46 -0800522 */
Felipe Leme81f01d92017-03-16 17:13:25 -0700523 public void notifyViewExited(@NonNull View view, int childId) {
Svet Ganov43574b02017-04-12 09:25:20 -0700524 if (!hasAutofillFeature()) {
525 return;
526 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700527 synchronized (mLock) {
528 ensureServiceClientAddedIfNeededLocked();
Philip P. Moltmann44611812017-02-23 12:52:46 -0800529
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700530 if (mEnabled && mSessionId != NO_SESSION) {
531 final AutofillId id = getAutofillId(view, childId);
Philip P. Moltmann44611812017-02-23 12:52:46 -0800532
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700533 // Update focus on existing session.
534 updateSessionLocked(id, null, null, FLAG_VIEW_EXITED);
535 }
Svet Ganov782043c2017-02-11 00:52:02 +0000536 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800537 }
538
539 /**
Felipe Leme640f30a2017-03-06 15:44:06 -0800540 * Called to indicate the value of an autofillable {@link View} changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800541 *
Philip P. Moltmann44611812017-02-23 12:52:46 -0800542 * @param view view whose value changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800543 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700544 public void notifyValueChanged(View view) {
Svet Ganov43574b02017-04-12 09:25:20 -0700545 if (!hasAutofillFeature()) {
546 return;
547 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700548 AutofillId id = null;
549 boolean valueWasRead = false;
550 AutofillValue value = null;
551
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700552 synchronized (mLock) {
553 // If the session is gone some fields might still be highlighted, hence we have to
554 // remove the isAutofilled property even if no sessions are active.
555 if (mLastAutofilledData == null) {
556 view.setAutofilled(false);
557 } else {
558 id = getAutofillId(view);
559 if (mLastAutofilledData.containsKey(id)) {
560 value = view.getAutofillValue();
561 valueWasRead = true;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700562
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700563 if (Objects.equals(mLastAutofilledData.get(id), value)) {
564 view.setAutofilled(true);
565 } else {
566 view.setAutofilled(false);
567 mLastAutofilledData.remove(id);
568 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700569 } else {
570 view.setAutofilled(false);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700571 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700572 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700573
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700574 if (!mEnabled || mSessionId == NO_SESSION) {
575 return;
576 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800577
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700578 if (id == null) {
579 id = getAutofillId(view);
580 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700581
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700582 if (!valueWasRead) {
583 value = view.getAutofillValue();
584 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700585
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700586 updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED);
587 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800588 }
589
Felipe Lemebab851c2017-02-03 18:45:08 -0800590 /**
Felipe Leme640f30a2017-03-06 15:44:06 -0800591 * Called to indicate the value of an autofillable virtual {@link View} changed.
Felipe Lemebab851c2017-02-03 18:45:08 -0800592 *
Felipe Leme2ac463e2017-03-13 14:06:25 -0700593 * @param view the {@link View} whose descendant is the virtual view.
Felipe Lemebab851c2017-02-03 18:45:08 -0800594 * @param childId id identifying the virtual child inside the parent view.
595 * @param value new value of the child.
596 */
Felipe Leme81f01d92017-03-16 17:13:25 -0700597 public void notifyValueChanged(View view, int childId, AutofillValue value) {
Svet Ganov43574b02017-04-12 09:25:20 -0700598 if (!hasAutofillFeature()) {
599 return;
600 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700601 synchronized (mLock) {
602 if (!mEnabled || mSessionId == NO_SESSION) {
603 return;
604 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800605
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700606 final AutofillId id = getAutofillId(view, childId);
607 updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED);
608 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800609 }
610
611 /**
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700612 * Called to indicate the current autofill context should be commited.
Felipe Lemebab851c2017-02-03 18:45:08 -0800613 *
614 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
615 * call this method after the form is submitted and another page is rendered.
616 */
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700617 public void commit() {
Svet Ganov43574b02017-04-12 09:25:20 -0700618 if (!hasAutofillFeature()) {
619 return;
620 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700621 synchronized (mLock) {
622 if (!mEnabled && mSessionId == NO_SESSION) {
623 return;
624 }
Svet Ganov782043c2017-02-11 00:52:02 +0000625
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700626 finishSessionLocked();
627 }
Felipe Leme0200d9e2017-01-24 15:10:26 -0800628 }
629
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700630 /**
631 * Called to indicate the current autofill context should be cancelled.
632 *
633 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
634 * call this method if the user does not post the form but moves to another form in this page.
635 */
636 public void cancel() {
Svet Ganov43574b02017-04-12 09:25:20 -0700637 if (!hasAutofillFeature()) {
638 return;
639 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700640 synchronized (mLock) {
641 if (!mEnabled && mSessionId == NO_SESSION) {
642 return;
643 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700644
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700645 cancelSessionLocked();
646 }
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700647 }
648
Svet Ganovf20a0372017-04-10 17:08:05 -0700649 /**
650 * If the app calling this API has enabled autofill services they
651 * will be disabled.
652 */
653 public void disableOwnedAutofillServices() {
Svet Ganov43574b02017-04-12 09:25:20 -0700654 if (!hasAutofillFeature()) {
655 return;
656 }
Svet Ganovf20a0372017-04-10 17:08:05 -0700657 try {
658 mService.disableOwnedAutofillServices(mContext.getUserId());
659 } catch (RemoteException e) {
660 throw e.rethrowFromSystemServer();
661 }
662 }
663
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700664 private AutofillClient getClientLocked() {
Felipe Leme640f30a2017-03-06 15:44:06 -0800665 if (mContext instanceof AutofillClient) {
666 return (AutofillClient) mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000667 }
Svet Ganov17db9dc2017-02-21 19:54:31 -0800668 return null;
Svet Ganov782043c2017-02-11 00:52:02 +0000669 }
670
671 /** @hide */
672 public void onAuthenticationResult(Intent data) {
Svet Ganov43574b02017-04-12 09:25:20 -0700673 if (!hasAutofillFeature()) {
674 return;
675 }
Felipe Leme85d1c2d2017-04-21 08:56:04 -0700676 // TODO: the result code is being ignored, so this method is not reliably
Felipe Lemed633f072017-02-14 10:17:17 -0800677 // handling the cases where it's not RESULT_OK: it works fine if the service does not
678 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
679 // service set the extra and returned RESULT_CANCELED...
680
681 if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data);
682
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700683 synchronized (mLock) {
684 if (mSessionId == NO_SESSION || data == null) {
685 return;
686 }
687 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
688 final Bundle responseData = new Bundle();
689 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
690 try {
691 mService.setAuthenticationResult(responseData, mSessionId, mContext.getUserId());
692 } catch (RemoteException e) {
693 Log.e(TAG, "Error delivering authentication result", e);
694 }
Svet Ganov782043c2017-02-11 00:52:02 +0000695 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800696 }
697
Felipe Leme640f30a2017-03-06 15:44:06 -0800698 private static AutofillId getAutofillId(View view) {
699 return new AutofillId(view.getAccessibilityViewId());
Felipe Leme0200d9e2017-01-24 15:10:26 -0800700 }
701
Felipe Leme640f30a2017-03-06 15:44:06 -0800702 private static AutofillId getAutofillId(View parent, int childId) {
703 return new AutofillId(parent.getAccessibilityViewId(), childId);
Felipe Lemebab851c2017-02-03 18:45:08 -0800704 }
705
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700706 private void startSessionLocked(@NonNull AutofillId id, @NonNull IBinder windowToken,
Philip P. Moltmann7b771162017-03-03 17:22:57 -0800707 @NonNull Rect bounds, @NonNull AutofillValue value, int flags) {
Svet Ganov782043c2017-02-11 00:52:02 +0000708 if (DEBUG) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700709 Log.d(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
Felipe Leme2ac463e2017-03-13 14:06:25 -0700710 + ", flags=" + flags);
Felipe Lemebab851c2017-02-03 18:45:08 -0800711 }
Felipe Lemee6010f22017-03-03 11:19:51 -0800712
Felipe Lemebab851c2017-02-03 18:45:08 -0800713 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700714 mSessionId = mService.startSession(mContext.getActivityToken(), windowToken,
Felipe Lemee6010f22017-03-03 11:19:51 -0800715 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
Philip P. Moltmann7b771162017-03-03 17:22:57 -0800716 mCallback != null, flags, mContext.getOpPackageName());
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700717 AutofillClient client = getClientLocked();
Svet Ganov17db9dc2017-02-21 19:54:31 -0800718 if (client != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700719 client.autofillCallbackResetableStateAvailable();
Svet Ganov17db9dc2017-02-21 19:54:31 -0800720 }
Svet Ganov782043c2017-02-11 00:52:02 +0000721 } catch (RemoteException e) {
722 throw e.rethrowFromSystemServer();
723 }
724 }
725
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700726 private void finishSessionLocked() {
Svet Ganov782043c2017-02-11 00:52:02 +0000727 if (DEBUG) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700728 Log.d(TAG, "finishSessionLocked()");
Svet Ganov782043c2017-02-11 00:52:02 +0000729 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700730
Svet Ganov782043c2017-02-11 00:52:02 +0000731 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700732 mService.finishSession(mSessionId, mContext.getUserId());
Felipe Lemebab851c2017-02-03 18:45:08 -0800733 } catch (RemoteException e) {
734 throw e.rethrowFromSystemServer();
735 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700736
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700737 mTrackedViews = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700738 mSessionId = NO_SESSION;
Felipe Lemebab851c2017-02-03 18:45:08 -0800739 }
740
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700741 private void cancelSessionLocked() {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700742 if (DEBUG) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700743 Log.d(TAG, "cancelSessionLocked()");
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700744 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700745
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700746 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700747 mService.cancelSession(mSessionId, mContext.getUserId());
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700748 } catch (RemoteException e) {
749 throw e.rethrowFromSystemServer();
750 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700751
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700752 mTrackedViews = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700753 mSessionId = NO_SESSION;
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700754 }
755
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700756 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
Svet Ganov782043c2017-02-11 00:52:02 +0000757 if (DEBUG) {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700758 if (VERBOSE || (flags & FLAG_VIEW_EXITED) != 0) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700759 Log.d(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
760 + ", value=" + value + ", flags=" + flags);
Felipe Leme0ab53dc2017-02-23 08:33:18 -0800761 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800762 }
Felipe Leme0ab53dc2017-02-23 08:33:18 -0800763
Felipe Leme3461d3c2017-01-19 08:54:55 -0800764 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700765 mService.updateSession(mSessionId, id, bounds, value, flags, mContext.getUserId());
Felipe Leme3461d3c2017-01-19 08:54:55 -0800766 } catch (RemoteException e) {
767 throw e.rethrowFromSystemServer();
768 }
769 }
Svet Ganov782043c2017-02-11 00:52:02 +0000770
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700771 private void ensureServiceClientAddedIfNeededLocked() {
772 if (getClientLocked() == null) {
Svet Ganov782043c2017-02-11 00:52:02 +0000773 return;
774 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700775
Svet Ganov782043c2017-02-11 00:52:02 +0000776 if (mServiceClient == null) {
Felipe Leme640f30a2017-03-06 15:44:06 -0800777 mServiceClient = new AutofillManagerClient(this);
Svet Ganov782043c2017-02-11 00:52:02 +0000778 try {
779 mEnabled = mService.addClient(mServiceClient, mContext.getUserId());
780 } catch (RemoteException e) {
781 throw e.rethrowFromSystemServer();
782 }
783 }
784 }
785
Felipe Lemee6010f22017-03-03 11:19:51 -0800786 /**
787 * Registers a {@link AutofillCallback} to receive autofill events.
788 *
789 * @param callback callback to receive events.
790 */
791 public void registerCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -0700792 if (!hasAutofillFeature()) {
793 return;
794 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700795 synchronized (mLock) {
796 if (callback == null) return;
Felipe Lemee6010f22017-03-03 11:19:51 -0800797
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700798 final boolean hadCallback = mCallback != null;
799 mCallback = callback;
Felipe Lemee6010f22017-03-03 11:19:51 -0800800
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700801 if (!hadCallback) {
802 try {
803 mService.setHasCallback(mSessionId, mContext.getUserId(), true);
804 } catch (RemoteException e) {
805 throw e.rethrowFromSystemServer();
806 }
Felipe Lemee6010f22017-03-03 11:19:51 -0800807 }
808 }
809 }
810
811 /**
812 * Unregisters a {@link AutofillCallback} to receive autofill events.
813 *
814 * @param callback callback to stop receiving events.
815 */
816 public void unregisterCallback(@Nullable AutofillCallback callback) {
Svet Ganov43574b02017-04-12 09:25:20 -0700817 if (!hasAutofillFeature()) {
818 return;
819 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700820 synchronized (mLock) {
821 if (callback == null || mCallback == null || callback != mCallback) return;
Felipe Lemee6010f22017-03-03 11:19:51 -0800822
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700823 mCallback = null;
Felipe Lemee6010f22017-03-03 11:19:51 -0800824
Felipe Lemee6010f22017-03-03 11:19:51 -0800825 try {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700826 mService.setHasCallback(mSessionId, mContext.getUserId(), false);
Felipe Lemee6010f22017-03-03 11:19:51 -0800827 } catch (RemoteException e) {
828 throw e.rethrowFromSystemServer();
829 }
830 }
831 }
832
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700833 private void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id, int width,
834 int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700835 final View anchor = findAchorView(windowToken, id);
836 if (anchor == null) {
837 return;
838 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700839
840 AutofillCallback callback = null;
841 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700842 if (mSessionId == sessionId) {
843 AutofillClient client = getClientLocked();
844
845 if (client != null) {
846 if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
847 anchorBounds, presenter) && mCallback != null) {
848 callback = mCallback;
849 }
850 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700851 }
852 }
853
854 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -0700855 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700856 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -0700857 AutofillCallback.EVENT_INPUT_SHOWN);
858 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700859 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
Felipe Leme4753bb02017-03-22 20:24:00 -0700860 }
861 }
862 }
863
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700864 private void authenticate(int sessionId, IntentSender intent, Intent fillInIntent) {
865 synchronized (mLock) {
866 if (sessionId == mSessionId) {
867 AutofillClient client = getClientLocked();
868 if (client != null) {
869 client.autofillCallbackAuthenticate(intent, fillInIntent);
870 }
871 }
872 }
873 }
874
875 private void setState(boolean enabled) {
876 synchronized (mLock) {
877 mEnabled = enabled;
878 }
879 }
880
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700881 /**
882 * Sets a view as autofilled if the current value is the {code targetValue}.
883 *
884 * @param view The view that is to be autofilled
885 * @param targetValue The value we want to fill into view
886 */
887 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
888 AutofillValue currentValue = view.getAutofillValue();
889 if (Objects.equals(currentValue, targetValue)) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700890 synchronized (mLock) {
891 if (mLastAutofilledData == null) {
892 mLastAutofilledData = new ParcelableMap(1);
893 }
894 mLastAutofilledData.put(getAutofillId(view), targetValue);
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700895 }
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700896 view.setAutofilled(true);
897 }
898 }
899
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700900 private void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
Felipe Leme4753bb02017-03-22 20:24:00 -0700901 List<AutofillValue> values) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700902 synchronized (mLock) {
903 if (sessionId != mSessionId) {
904 return;
Felipe Leme4753bb02017-03-22 20:24:00 -0700905 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700906
907 final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
908 if (root == null) {
909 return;
910 }
911
912 final int itemCount = ids.size();
913 int numApplied = 0;
914 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
915
916 for (int i = 0; i < itemCount; i++) {
917 final AutofillId id = ids.get(i);
918 final AutofillValue value = values.get(i);
919 final int viewId = id.getViewId();
920 final View view = root.findViewByAccessibilityIdTraversal(viewId);
921 if (view == null) {
922 Log.w(TAG, "autofill(): no View with id " + viewId);
923 continue;
Felipe Leme4753bb02017-03-22 20:24:00 -0700924 }
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700925 if (id.isVirtual()) {
926 if (virtualValues == null) {
927 // Most likely there will be just one view with virtual children.
928 virtualValues = new ArrayMap<>(1);
929 }
930 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
931 if (valuesByParent == null) {
932 // We don't know the size yet, but usually it will be just a few fields...
933 valuesByParent = new SparseArray<>(5);
934 virtualValues.put(view, valuesByParent);
935 }
936 valuesByParent.put(id.getVirtualChildId(), value);
937 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700938 // Mark the view as to be autofilled with 'value'
939 if (mLastAutofilledData == null) {
940 mLastAutofilledData = new ParcelableMap(itemCount - i);
941 }
942 mLastAutofilledData.put(id, value);
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700943
944 view.autofill(value);
945
946 // Set as autofilled if the values match now, e.g. when the value was updated
947 // synchronously.
948 // If autofill happens async, the view is set to autofilled in
949 // notifyValueChanged.
950 setAutofilledIfValuesIs(view, value);
951
952 numApplied++;
Philip P. Moltmannb42d1332017-03-27 09:55:30 -0700953 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700954 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700955
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700956 if (virtualValues != null) {
957 for (int i = 0; i < virtualValues.size(); i++) {
958 final View parent = virtualValues.keyAt(i);
959 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
960 parent.autofill(childrenValues);
961 numApplied += childrenValues.size();
962 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700963 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700964
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700965 final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
966 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
967 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
968 numApplied);
969 mMetricsLogger.write(log);
970 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700971 }
972
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700973 /**
974 * Set the tracked views.
975 *
976 * @param trackedIds The views to be tracked
977 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
978 */
979 private void setTrackedViews(int sessionId, List<AutofillId> trackedIds,
980 boolean saveOnAllViewsInvisible) {
981 synchronized (mLock) {
982 if (mEnabled && mSessionId == sessionId) {
983 if (saveOnAllViewsInvisible) {
984 mTrackedViews = new TrackedViews(trackedIds);
985 } else {
986 mTrackedViews = null;
987 }
988 }
989 }
990 }
991
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700992 private void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -0700993 final View anchor = findAchorView(windowToken, id);
994
995 AutofillCallback callback = null;
996 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -0700997 if (mSessionId == sessionId) {
998 AutofillClient client = getClientLocked();
999
1000 if (client != null) {
1001 if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
1002 callback = mCallback;
1003 }
1004 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001005 }
1006 }
1007
1008 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001009 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001010 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001011 AutofillCallback.EVENT_INPUT_HIDDEN);
1012 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001013 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
Felipe Leme4753bb02017-03-22 20:24:00 -07001014 }
1015 }
1016 }
1017
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001018 private void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001019 final View anchor = findAchorView(windowToken, id);
1020
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001021 AutofillCallback callback = null;
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001022 synchronized (mLock) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001023 if (mSessionId == sessionId && getClientLocked() != null) {
1024 callback = mCallback;
1025 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001026 }
1027
1028 if (callback != null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001029 if (id.isVirtual()) {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001030 callback.onAutofillEvent(anchor, id.getVirtualChildId(),
Felipe Leme4753bb02017-03-22 20:24:00 -07001031 AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1032 } else {
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001033 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
Felipe Leme4753bb02017-03-22 20:24:00 -07001034 }
Philip P. Moltmanneab62ba2017-03-20 10:55:43 -07001035
Felipe Leme4753bb02017-03-22 20:24:00 -07001036 }
1037 }
1038
1039 private View findAchorView(IBinder windowToken, AutofillId id) {
Felipe Lemee6010f22017-03-03 11:19:51 -08001040 final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
1041 if (root == null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001042 Log.w(TAG, "no window with token " + windowToken);
1043 return null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001044 }
1045 final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
1046 if (view == null) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001047 Log.w(TAG, "no view with id " + id);
1048 return null;
Felipe Lemee6010f22017-03-03 11:19:51 -08001049 }
Felipe Leme4753bb02017-03-22 20:24:00 -07001050 return view;
Felipe Lemee6010f22017-03-03 11:19:51 -08001051 }
1052
Svet Ganov43574b02017-04-12 09:25:20 -07001053 private boolean hasAutofillFeature() {
1054 return mService != null;
1055 }
1056
Felipe Lemee6010f22017-03-03 11:19:51 -08001057 /**
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001058 * View tracking information. Once all tracked views become invisible the session is finished.
1059 */
1060 private class TrackedViews {
1061 /** Visible tracked views */
1062 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
1063
1064 /** Invisible tracked views */
1065 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
1066
1067 /**
1068 * Check if set is null or value is in set.
1069 *
1070 * @param set The set or null (== empty set)
1071 * @param value The value that might be in the set
1072 *
1073 * @return {@code true} iff set is not empty and value is in set
1074 */
1075 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
1076 return set != null && set.contains(value);
1077 }
1078
1079 /**
1080 * Add a value to a set. If set is null, create a new set.
1081 *
1082 * @param set The set or null (== empty set)
1083 * @param valueToAdd The value to add
1084 *
1085 * @return The set including the new value. If set was {@code null}, a set containing only
1086 * the new value.
1087 */
1088 @NonNull
1089 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
1090 if (set == null) {
1091 set = new ArraySet<>(1);
1092 }
1093
1094 set.add(valueToAdd);
1095
1096 return set;
1097 }
1098
1099 /**
1100 * Remove a value from a set.
1101 *
1102 * @param set The set or null (== empty set)
1103 * @param valueToRemove The value to remove
1104 *
1105 * @return The set without the removed value. {@code null} if set was null, or is empty
1106 * after removal.
1107 */
1108 @Nullable
1109 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
1110 if (set == null) {
1111 return null;
1112 }
1113
1114 set.remove(valueToRemove);
1115
1116 if (set.isEmpty()) {
1117 return null;
1118 }
1119
1120 return set;
1121 }
1122
1123 /**
1124 * Set the tracked views.
1125 *
1126 * @param trackedIds The views to be tracked
1127 */
1128 TrackedViews(@NonNull List<AutofillId> trackedIds) {
1129 mVisibleTrackedIds = null;
1130 mInvisibleTrackedIds = null;
1131
1132 AutofillClient client = getClientLocked();
1133 if (trackedIds != null) {
1134 int numIds = trackedIds.size();
1135 for (int i = 0; i < numIds; i++) {
1136 AutofillId id = trackedIds.get(i);
1137
1138 boolean isVisible = true;
1139 if (client != null && client.isVisibleForAutofill()) {
1140 isVisible = client.getViewVisibility(id.getViewId());
1141 }
1142
1143 if (isVisible) {
Philip P. Moltmanne0e28712017-04-20 15:19:06 -07001144 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001145 } else {
1146 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1147 }
1148 }
1149 }
1150
1151 if (DEBUG) {
1152 Log.d(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
1153 + " mVisibleTrackedIds=" + mVisibleTrackedIds
1154 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
1155 }
1156
1157 if (mVisibleTrackedIds == null) {
1158 finishSessionLocked();
1159 }
1160 }
1161
1162 /**
1163 * Called when a {@link View view's} visibility changes.
1164 *
1165 * @param view {@link View} that was exited.
1166 * @param isVisible visible if the view is visible in the view hierarchy.
1167 */
1168 void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) {
1169 AutofillId id = getAutofillId(view);
1170 AutofillClient client = getClientLocked();
1171
1172 if (DEBUG) {
1173 Log.d(TAG, "notifyViewVisibilityChange(): id=" + id + " isVisible="
1174 + isVisible);
1175 }
1176
1177 if (client != null && client.isVisibleForAutofill()) {
1178 if (isVisible) {
1179 if (isInSet(mInvisibleTrackedIds, id)) {
1180 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
1181 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
1182 }
1183 } else {
1184 if (isInSet(mVisibleTrackedIds, id)) {
1185 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
1186 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
1187 }
1188 }
1189 }
1190
1191 if (mVisibleTrackedIds == null) {
1192 finishSessionLocked();
1193 }
1194 }
1195
1196 /**
1197 * Called once the client becomes visible.
1198 *
1199 * @see AutofillClient#isVisibleForAutofill()
1200 */
1201 void onVisibleForAutofill() {
1202 // The visibility of the views might have changed while the client was not started,
1203 // hence update the visibility state for all views.
1204 AutofillClient client = getClientLocked();
1205 ArraySet<AutofillId> updatedVisibleTrackedIds = null;
1206 ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
1207 if (client != null) {
1208 if (mInvisibleTrackedIds != null) {
1209 for (AutofillId id : mInvisibleTrackedIds) {
1210 if (client.getViewVisibility(id.getViewId())) {
1211 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1212
1213 if (DEBUG) {
1214 Log.i(TAG, "onVisibleForAutofill() " + id + " became visible");
1215 }
1216 } else {
1217 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1218 }
1219 }
1220 }
1221
1222 if (mVisibleTrackedIds != null) {
1223 for (AutofillId id : mVisibleTrackedIds) {
1224 if (client.getViewVisibility(id.getViewId())) {
1225 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
1226 } else {
1227 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
1228
1229 if (DEBUG) {
1230 Log.i(TAG, "onVisibleForAutofill() " + id + " became invisible");
1231 }
1232 }
1233 }
1234 }
1235
1236 mInvisibleTrackedIds = updatedInvisibleTrackedIds;
1237 mVisibleTrackedIds = updatedVisibleTrackedIds;
1238 }
1239
1240 if (mVisibleTrackedIds == null) {
1241 finishSessionLocked();
1242 }
1243 }
1244 }
1245
1246 /**
Felipe Lemee6010f22017-03-03 11:19:51 -08001247 * Callback for auto-fill related events.
1248 *
1249 * <p>Typically used for applications that display their own "auto-complete" views, so they can
1250 * enable / disable such views when the auto-fill UI affordance is shown / hidden.
1251 */
1252 public abstract static class AutofillCallback {
1253
1254 /** @hide */
1255 @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
1256 @Retention(RetentionPolicy.SOURCE)
1257 public @interface AutofillEventType {}
1258
1259 /**
1260 * The auto-fill input UI affordance associated with the view was shown.
1261 *
1262 * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
1263 * should be hidden upon receiving this event.
1264 */
1265 public static final int EVENT_INPUT_SHOWN = 1;
1266
1267 /**
1268 * The auto-fill input UI affordance associated with the view was hidden.
1269 *
1270 * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
1271 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
1272 */
1273 public static final int EVENT_INPUT_HIDDEN = 2;
1274
1275 /**
Felipe Leme24aae152017-03-15 12:33:01 -07001276 * The auto-fill input UI affordance associated with the view won't be shown because
1277 * autofill is not available.
1278 *
1279 * <p>If the view provides its own auto-complete UI affordance but was not displaying it
1280 * to avoid flickering, it could shown it upon receiving this event.
1281 */
1282 public static final int EVENT_INPUT_UNAVAILABLE = 3;
1283
1284 /**
Felipe Lemee6010f22017-03-03 11:19:51 -08001285 * Called after a change in the autofill state associated with a view.
1286 *
1287 * @param view view associated with the change.
1288 *
1289 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1290 */
Felipe Leme81f01d92017-03-16 17:13:25 -07001291 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
1292 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001293
1294 /**
1295 * Called after a change in the autofill state associated with a virtual view.
1296 *
1297 * @param view parent view associated with the change.
1298 * @param childId id identifying the virtual child inside the parent view.
1299 *
1300 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
1301 */
Felipe Leme81f01d92017-03-16 17:13:25 -07001302 public void onAutofillEvent(@NonNull View view, int childId, @AutofillEventType int event) {
1303 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001304 }
1305
Felipe Leme640f30a2017-03-06 15:44:06 -08001306 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
1307 private final WeakReference<AutofillManager> mAfm;
Svet Ganov782043c2017-02-11 00:52:02 +00001308
Felipe Leme640f30a2017-03-06 15:44:06 -08001309 AutofillManagerClient(AutofillManager autofillManager) {
1310 mAfm = new WeakReference<>(autofillManager);
Svet Ganov782043c2017-02-11 00:52:02 +00001311 }
1312
1313 @Override
1314 public void setState(boolean enabled) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001315 final AutofillManager afm = mAfm.get();
1316 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001317 afm.mContext.getMainThreadHandler().post(() -> afm.setState(enabled));
Svet Ganov782043c2017-02-11 00:52:02 +00001318 }
1319 }
1320
1321 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001322 public void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
Felipe Leme4753bb02017-03-22 20:24:00 -07001323 List<AutofillValue> values) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001324 final AutofillManager afm = mAfm.get();
1325 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001326 afm.mContext.getMainThreadHandler().post(
1327 () -> afm.autofill(sessionId, windowToken, ids, values));
Svet Ganov782043c2017-02-11 00:52:02 +00001328 }
1329 }
1330
1331 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001332 public void authenticate(int sessionId, IntentSender intent, Intent fillInIntent) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001333 final AutofillManager afm = mAfm.get();
1334 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001335 afm.mContext.getMainThreadHandler().post(
1336 () -> afm.authenticate(sessionId, intent, fillInIntent));
Svet Ganov782043c2017-02-11 00:52:02 +00001337 }
1338 }
Felipe Lemee6010f22017-03-03 11:19:51 -08001339
1340 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001341 public void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id,
Felipe Leme4753bb02017-03-22 20:24:00 -07001342 int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
Felipe Leme640f30a2017-03-06 15:44:06 -08001343 final AutofillManager afm = mAfm.get();
1344 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001345 afm.mContext.getMainThreadHandler().post(
1346 () -> afm.requestShowFillUi(sessionId, windowToken, id, width, height,
1347 anchorBounds, presenter));
Felipe Leme4753bb02017-03-22 20:24:00 -07001348 }
1349 }
1350
1351 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001352 public void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001353 final AutofillManager afm = mAfm.get();
1354 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001355 afm.mContext.getMainThreadHandler().post(
1356 () -> afm.requestHideFillUi(sessionId, windowToken, id));
Felipe Leme4753bb02017-03-22 20:24:00 -07001357 }
1358 }
1359
1360 @Override
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001361 public void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
Felipe Leme4753bb02017-03-22 20:24:00 -07001362 final AutofillManager afm = mAfm.get();
1363 if (afm != null) {
Philip P. Moltmanne048a652017-04-11 10:13:33 -07001364 afm.mContext.getMainThreadHandler().post(
1365 () -> afm.notifyNoFillUi(sessionId, windowToken, id));
Felipe Lemee6010f22017-03-03 11:19:51 -08001366 }
1367 }
Svet Ganovc3d1c852017-04-12 22:34:28 -07001368
1369 @Override
1370 public void startIntentSender(IntentSender intentSender) {
1371 final AutofillManager afm = mAfm.get();
1372 if (afm != null) {
1373 afm.mContext.getMainThreadHandler().post(() -> {
1374 try {
1375 afm.mContext.startIntentSender(intentSender, null, 0, 0, 0);
1376 } catch (IntentSender.SendIntentException e) {
1377 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
1378 }
1379 });
1380 }
1381 }
Philip P. Moltmann494c3f52017-04-11 10:13:33 -07001382
1383 @Override
1384 public void setTrackedViews(int sessionId, List<AutofillId> ids,
1385 boolean saveOnAllViewsInvisible) {
1386 final AutofillManager afm = mAfm.get();
1387 if (afm != null) {
1388 afm.mContext.getMainThreadHandler().post(
1389 () -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible)
1390 );
1391 }
1392 }
Svet Ganov782043c2017-02-11 00:52:02 +00001393 }
Felipe Leme3461d3c2017-01-19 08:54:55 -08001394}