blob: d9003a6fa3bdff6527e0019fd1ffd17ed5ef2980 [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;
20
Felipe Leme3461d3c2017-01-19 08:54:55 -080021import android.content.Context;
Svet Ganov782043c2017-02-11 00:52:02 +000022import android.content.Intent;
23import android.content.IntentSender;
Felipe Leme3461d3c2017-01-19 08:54:55 -080024import android.graphics.Rect;
Svet Ganov782043c2017-02-11 00:52:02 +000025import android.os.Bundle;
Felipe Lemebab851c2017-02-03 18:45:08 -080026import android.os.IBinder;
Svet Ganov782043c2017-02-11 00:52:02 +000027import android.os.Parcelable;
Felipe Leme3461d3c2017-01-19 08:54:55 -080028import android.os.RemoteException;
Svet Ganov782043c2017-02-11 00:52:02 +000029import android.util.ArrayMap;
Felipe Leme3461d3c2017-01-19 08:54:55 -080030import android.util.Log;
31import android.view.View;
32
Svet Ganov782043c2017-02-11 00:52:02 +000033import java.lang.ref.WeakReference;
34import java.util.List;
35
Felipe Leme3461d3c2017-01-19 08:54:55 -080036/**
37 * App entry point to the AutoFill Framework.
38 */
39// TODO(b/33197203): improve this javadoc
Felipe Lemebab851c2017-02-03 18:45:08 -080040//TODO(b/33197203): restrict manager calls to activity
Felipe Leme3461d3c2017-01-19 08:54:55 -080041public final class AutoFillManager {
42
43 private static final String TAG = "AutoFillManager";
Felipe Leme3461d3c2017-01-19 08:54:55 -080044
Svet Ganov782043c2017-02-11 00:52:02 +000045 /**
46 * Intent extra: The assist structure which captures the filled screen.
47 * <p>
48 * Type: {@link android.app.assist.AssistStructure}
49 * </p>
50 */
51 public static final String EXTRA_ASSIST_STRUCTURE =
52 "android.view.autofill.extra.ASSIST_STRUCTURE";
53
54 /**
55 * Intent extra: The result of an authentication operation. It is
56 * either a fully populated {@link android.service.autofill.FillResponse}
57 * or a fully populated {@link android.service.autofill.Dataset} if
58 * a response or a dataset is being authenticated respectively.
59 *
60 * <p>
61 * Type: {@link android.service.autofill.FillResponse} or a
62 * {@link android.service.autofill.Dataset}
63 * </p>
64 */
65 public static final String EXTRA_AUTHENTICATION_RESULT =
66 "android.view.autofill.extra.AUTHENTICATION_RESULT";
67
Felipe Lemebab851c2017-02-03 18:45:08 -080068 /** @hide */ public static final int FLAG_START_SESSION = 0x1;
69 /** @hide */ public static final int FLAG_FOCUS_GAINED = 0x2;
70 /** @hide */ public static final int FLAG_FOCUS_LOST = 0x4;
71 /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x8;
Felipe Leme3461d3c2017-01-19 08:54:55 -080072
Svet Ganov782043c2017-02-11 00:52:02 +000073 // These are activities that may have auto-fill UI which are keyed off their tokens.
74 // This is done instead of the activity setting the client in the auto-fill manager
75 // to avoid unnecessary instantiation of the manager and do this only if there is an
76 // auto-fillable focused. This has only the cost of loading the class vs creating an
77 // auto-fill manager for every activity even one that cannot be filled.
78 private static final ArrayMap<IBinder, AutoFillClient> sPendingClients = new ArrayMap<>();
Felipe Lemebab851c2017-02-03 18:45:08 -080079
Svet Ganov782043c2017-02-11 00:52:02 +000080 private final Rect mTempRect = new Rect();
81
82 private final IAutoFillManager mService;
83 private IAutoFillManagerClient mServiceClient;
84
85 private Context mContext;
86
87 private AutoFillClient mClient;
88
89 private boolean mHasSession;
90 private boolean mEnabled;
91
92 /** @hide */
93 public interface AutoFillClient {
94 /**
95 * Asks the client to perform an auto-fill.
96 *
97 * @param ids The values to auto-fill
98 * @param values The values to auto-fill
99 */
100 void autoFill(List<AutoFillId> ids, List<AutoFillValue> values);
101
102 /**
103 * Asks the client to start an authentication flow.
104 *
105 * @param intent The authentication intent.
106 * @param fillInIntent The authentication fill-in intent.
107 */
108 void authenticate(IntentSender intent, Intent fillInIntent);
109 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800110
111 /**
112 * @hide
113 */
Svet Ganov782043c2017-02-11 00:52:02 +0000114 public AutoFillManager(Context context, IAutoFillManager service) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800115 mContext = context;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800116 mService = service;
117 }
118
119 /**
Felipe Lemebab851c2017-02-03 18:45:08 -0800120 * Called to indicate the focus on an auto-fillable {@link View} changed.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800121 *
Felipe Lemebab851c2017-02-03 18:45:08 -0800122 * @param view view whose focus changed.
123 * @param gainFocus whether focus was gained or lost.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800124 */
Felipe Lemebab851c2017-02-03 18:45:08 -0800125 public void focusChanged(View view, boolean gainFocus) {
Svet Ganov782043c2017-02-11 00:52:02 +0000126 ensureServiceClientAddedIfNeeded();
127
128 if (!mEnabled) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800129 return;
130 }
131
Svet Ganov782043c2017-02-11 00:52:02 +0000132 final Rect bounds = mTempRect;
Felipe Lemebd00fef2017-01-24 15:10:26 -0800133 view.getBoundsOnScreen(bounds);
Felipe Leme0200d9e2017-01-24 15:10:26 -0800134 final AutoFillId id = getAutoFillId(view);
Felipe Lemebab851c2017-02-03 18:45:08 -0800135 final AutoFillValue value = view.getAutoFillValue();
Svet Ganov782043c2017-02-11 00:52:02 +0000136
137 if (!mHasSession) {
138 if (gainFocus) {
139 // Starts new session.
140 startSession(id, bounds, value);
141 }
142 } else {
143 // Update focus on existing session.
144 updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
145 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800146 }
147
148 /**
149 * Called to indicate the focus on an auto-fillable virtual {@link View} changed.
150 *
151 * @param parent parent view whose focus changed.
152 * @param childId id identifying the virtual child inside the parent view.
153 * @param bounds child boundaries, relative to the top window.
Felipe Lemebab851c2017-02-03 18:45:08 -0800154 * @param gainFocus whether focus was gained or lost.
155 */
Felipe Leme7f60d352017-02-07 17:14:52 -0800156 public void virtualFocusChanged(View parent, int childId, Rect bounds, boolean gainFocus) {
Svet Ganov782043c2017-02-11 00:52:02 +0000157 ensureServiceClientAddedIfNeeded();
158
159 if (!mEnabled) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800160 return;
161 }
162
Felipe Lemebab851c2017-02-03 18:45:08 -0800163 final AutoFillId id = getAutoFillId(parent, childId);
Svet Ganov782043c2017-02-11 00:52:02 +0000164
165 if (!mHasSession) {
166 if (gainFocus) {
167 // Starts new session.
168 startSession(id, bounds, null);
169 }
170 } else {
171 // Update focus on existing session.
172 updateSession(id, bounds, null, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
173 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800174 }
175
176 /**
177 * Called to indicate the value of an auto-fillable {@link View} changed.
178 *
179 * @param view view whose focus changed.
180 */
181 public void valueChanged(View view) {
Svet Ganov782043c2017-02-11 00:52:02 +0000182 ensureServiceClientAddedIfNeeded();
183
184 if (!mEnabled || !mHasSession) {
185 return;
186 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800187
188 final AutoFillId id = getAutoFillId(view);
189 final AutoFillValue value = view.getAutoFillValue();
190 updateSession(id, null, value, FLAG_VALUE_CHANGED);
191 }
192
193
194 /**
195 * Called to indicate the value of an auto-fillable virtual {@link View} changed.
196 *
197 * @param parent parent view whose value changed.
198 * @param childId id identifying the virtual child inside the parent view.
199 * @param value new value of the child.
200 */
201 public void virtualValueChanged(View parent, int childId, AutoFillValue value) {
Svet Ganov782043c2017-02-11 00:52:02 +0000202 ensureServiceClientAddedIfNeeded();
203
204 if (!mEnabled || !mHasSession) {
205 return;
206 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800207
208 final AutoFillId id = getAutoFillId(parent, childId);
209 updateSession(id, null, value, FLAG_VALUE_CHANGED);
210 }
211
212 /**
213 * Called to indicate the current auto-fill context should be reset.
214 *
215 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
216 * call this method after the form is submitted and another page is rendered.
217 */
218 public void reset() {
Svet Ganov782043c2017-02-11 00:52:02 +0000219 ensureServiceClientAddedIfNeeded();
Felipe Lemebab851c2017-02-03 18:45:08 -0800220
Svet Ganov782043c2017-02-11 00:52:02 +0000221 if (!mEnabled && !mHasSession) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800222 return;
223 }
Svet Ganov782043c2017-02-11 00:52:02 +0000224
225 finishSession();
Felipe Leme0200d9e2017-01-24 15:10:26 -0800226 }
227
Svet Ganov782043c2017-02-11 00:52:02 +0000228 /** @hide */
229 public static void addClient(IBinder token, AutoFillClient client) {
230 sPendingClients.put(token, client);
231 }
232
233 /** @hide */
234 public static boolean isClientActive(IBinder token) {
235 return !sPendingClients.containsKey(token);
236 }
237
238 private void activateClient() {
239 mClient = sPendingClients.remove(mContext.getActivityToken());
240 }
241
242 private AutoFillClient getClient() {
243 if (mClient == null) {
244 return sPendingClients.get(mContext.getActivityToken());
245 }
246 return mClient;
247 }
248
249 /** @hide */
250 public void onAuthenticationResult(Intent data) {
Felipe Lemed633f072017-02-14 10:17:17 -0800251 // TODO(b/33197203): the result code is being ignored, so this method is not reliably
252 // handling the cases where it's not RESULT_OK: it works fine if the service does not
253 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
254 // service set the extra and returned RESULT_CANCELED...
255
256 if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data);
257
Svet Ganov782043c2017-02-11 00:52:02 +0000258 if (data == null) {
259 return;
260 }
Felipe Lemed633f072017-02-14 10:17:17 -0800261 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
262 final Bundle responseData = new Bundle();
Svet Ganov782043c2017-02-11 00:52:02 +0000263 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
264 try {
265 mService.setAuthenticationResult(responseData,
266 mContext.getActivityToken(), mContext.getUserId());
267 } catch (RemoteException e) {
268 Log.e(TAG, "Error delivering authentication result", e);
269 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800270 }
271
Felipe Leme0200d9e2017-01-24 15:10:26 -0800272 private AutoFillId getAutoFillId(View view) {
273 return new AutoFillId(view.getAccessibilityViewId());
274 }
275
Felipe Lemebab851c2017-02-03 18:45:08 -0800276 private AutoFillId getAutoFillId(View parent, int childId) {
277 return new AutoFillId(parent.getAccessibilityViewId(), childId);
278 }
279
280 private void startSession(AutoFillId id, Rect bounds, AutoFillValue value) {
Svet Ganov782043c2017-02-11 00:52:02 +0000281 if (DEBUG) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800282 Log.v(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value);
283 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800284 try {
Svet Ganov782043c2017-02-11 00:52:02 +0000285 mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(),
286 id, bounds, value, mContext.getUserId());
287 mHasSession = true;
288 activateClient();
289 } catch (RemoteException e) {
290 throw e.rethrowFromSystemServer();
291 }
292 }
293
294 private void finishSession() {
295 if (DEBUG) {
296 Log.v(TAG, "finishSession()");
297 }
298 mHasSession = false;
299 try {
300 mService.finishSession(mContext.getActivityToken(), mContext.getUserId());
Felipe Lemebab851c2017-02-03 18:45:08 -0800301 } catch (RemoteException e) {
302 throw e.rethrowFromSystemServer();
303 }
304 }
305
306 private void updateSession(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
Svet Ganov782043c2017-02-11 00:52:02 +0000307 if (DEBUG) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800308 Log.v(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value
309 + ", flags=" + flags);
310 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800311 try {
Svet Ganov782043c2017-02-11 00:52:02 +0000312 mService.updateSession(mContext.getActivityToken(), id, bounds, value, flags,
313 mContext.getUserId());
Felipe Leme3461d3c2017-01-19 08:54:55 -0800314 } catch (RemoteException e) {
315 throw e.rethrowFromSystemServer();
316 }
317 }
Svet Ganov782043c2017-02-11 00:52:02 +0000318
319 private void ensureServiceClientAddedIfNeeded() {
320 if (getClient() == null) {
321 return;
322 }
323 if (mServiceClient == null) {
324 mServiceClient = new AutoFillManagerClient(this);
325 try {
326 mEnabled = mService.addClient(mServiceClient, mContext.getUserId());
327 } catch (RemoteException e) {
328 throw e.rethrowFromSystemServer();
329 }
330 }
331 }
332
333 private static final class AutoFillManagerClient extends IAutoFillManagerClient.Stub {
334 private final WeakReference<AutoFillManager> mAutoFillManager;
335
336 AutoFillManagerClient(AutoFillManager autoFillManager) {
337 mAutoFillManager = new WeakReference<>(autoFillManager);
338 }
339
340 @Override
341 public void setState(boolean enabled) {
342 final AutoFillManager autoFillManager = mAutoFillManager.get();
343 if (autoFillManager != null) {
344 autoFillManager.mContext.getMainThreadHandler().post(() ->
345 autoFillManager.mEnabled = enabled);
346 }
347 }
348
349 @Override
350 public void autoFill(List<AutoFillId> ids, List<AutoFillValue> values) {
351 // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
352 // dataset.extras to service
353 final AutoFillManager autoFillManager = mAutoFillManager.get();
354 if (autoFillManager != null) {
355 autoFillManager.mContext.getMainThreadHandler().post(() -> {
356 if (autoFillManager.getClient() != null) {
357 autoFillManager.getClient().autoFill(ids, values);
358 }
359 });
360 }
361 }
362
363 @Override
364 public void authenticate(IntentSender intent, Intent fillInIntent) {
365 final AutoFillManager autoFillManager = mAutoFillManager.get();
366 if (autoFillManager != null) {
367 autoFillManager.mContext.getMainThreadHandler().post(() -> {
368 if (autoFillManager.getClient() != null) {
369 autoFillManager.getClient().authenticate(intent, fillInIntent);
370 }
371 });
372 }
373 }
374 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800375}