blob: baba3895ccca091883b5668449c34c6dcea6c6e6 [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;
Svet Ganov782043c2017-02-11 00:52:02 +000026import android.os.Parcelable;
Felipe Leme3461d3c2017-01-19 08:54:55 -080027import android.os.RemoteException;
Felipe Leme3461d3c2017-01-19 08:54:55 -080028import android.util.Log;
29import android.view.View;
30
Svet Ganov782043c2017-02-11 00:52:02 +000031import java.lang.ref.WeakReference;
32import java.util.List;
33
Felipe Leme3461d3c2017-01-19 08:54:55 -080034/**
35 * App entry point to the AutoFill Framework.
36 */
37// TODO(b/33197203): improve this javadoc
Felipe Lemebab851c2017-02-03 18:45:08 -080038//TODO(b/33197203): restrict manager calls to activity
Felipe Leme3461d3c2017-01-19 08:54:55 -080039public final class AutoFillManager {
40
41 private static final String TAG = "AutoFillManager";
Felipe Leme3461d3c2017-01-19 08:54:55 -080042
Svet Ganov782043c2017-02-11 00:52:02 +000043 /**
44 * Intent extra: The assist structure which captures the filled screen.
45 * <p>
46 * Type: {@link android.app.assist.AssistStructure}
47 * </p>
48 */
49 public static final String EXTRA_ASSIST_STRUCTURE =
50 "android.view.autofill.extra.ASSIST_STRUCTURE";
51
52 /**
53 * Intent extra: The result of an authentication operation. It is
54 * either a fully populated {@link android.service.autofill.FillResponse}
55 * or a fully populated {@link android.service.autofill.Dataset} if
56 * a response or a dataset is being authenticated respectively.
57 *
58 * <p>
59 * Type: {@link android.service.autofill.FillResponse} or a
60 * {@link android.service.autofill.Dataset}
61 * </p>
62 */
63 public static final String EXTRA_AUTHENTICATION_RESULT =
64 "android.view.autofill.extra.AUTHENTICATION_RESULT";
65
Felipe Lemebab851c2017-02-03 18:45:08 -080066 /** @hide */ public static final int FLAG_START_SESSION = 0x1;
67 /** @hide */ public static final int FLAG_FOCUS_GAINED = 0x2;
68 /** @hide */ public static final int FLAG_FOCUS_LOST = 0x4;
69 /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x8;
Felipe Leme3461d3c2017-01-19 08:54:55 -080070
Svet Ganov782043c2017-02-11 00:52:02 +000071 private final Rect mTempRect = new Rect();
72
73 private final IAutoFillManager mService;
74 private IAutoFillManagerClient mServiceClient;
75
76 private Context mContext;
77
Svet Ganov782043c2017-02-11 00:52:02 +000078 private boolean mHasSession;
79 private boolean mEnabled;
80
81 /** @hide */
82 public interface AutoFillClient {
83 /**
84 * Asks the client to perform an auto-fill.
85 *
86 * @param ids The values to auto-fill
87 * @param values The values to auto-fill
88 */
89 void autoFill(List<AutoFillId> ids, List<AutoFillValue> values);
90
91 /**
92 * Asks the client to start an authentication flow.
93 *
94 * @param intent The authentication intent.
95 * @param fillInIntent The authentication fill-in intent.
96 */
97 void authenticate(IntentSender intent, Intent fillInIntent);
Svet Ganov17db9dc2017-02-21 19:54:31 -080098
99 /**
100 * Tells the client this manager has state to be reset.
101 */
102 void resetableStateAvailable();
Svet Ganov782043c2017-02-11 00:52:02 +0000103 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800104
105 /**
106 * @hide
107 */
Svet Ganov782043c2017-02-11 00:52:02 +0000108 public AutoFillManager(Context context, IAutoFillManager service) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800109 mContext = context;
Felipe Leme3461d3c2017-01-19 08:54:55 -0800110 mService = service;
111 }
112
113 /**
Felipe Lemebab851c2017-02-03 18:45:08 -0800114 * Called to indicate the focus on an auto-fillable {@link View} changed.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800115 *
Felipe Lemebab851c2017-02-03 18:45:08 -0800116 * @param view view whose focus changed.
117 * @param gainFocus whether focus was gained or lost.
Felipe Leme3461d3c2017-01-19 08:54:55 -0800118 */
Felipe Lemebab851c2017-02-03 18:45:08 -0800119 public void focusChanged(View view, boolean gainFocus) {
Svet Ganov782043c2017-02-11 00:52:02 +0000120 ensureServiceClientAddedIfNeeded();
121
122 if (!mEnabled) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800123 return;
124 }
125
Svet Ganov782043c2017-02-11 00:52:02 +0000126 final Rect bounds = mTempRect;
Felipe Lemebd00fef2017-01-24 15:10:26 -0800127 view.getBoundsOnScreen(bounds);
Felipe Leme0200d9e2017-01-24 15:10:26 -0800128 final AutoFillId id = getAutoFillId(view);
Felipe Lemebab851c2017-02-03 18:45:08 -0800129 final AutoFillValue value = view.getAutoFillValue();
Svet Ganov782043c2017-02-11 00:52:02 +0000130
131 if (!mHasSession) {
132 if (gainFocus) {
133 // Starts new session.
134 startSession(id, bounds, value);
135 }
136 } else {
137 // Update focus on existing session.
138 updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
139 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800140 }
141
142 /**
143 * Called to indicate the focus on an auto-fillable virtual {@link View} changed.
144 *
145 * @param parent parent view whose focus changed.
146 * @param childId id identifying the virtual child inside the parent view.
147 * @param bounds child boundaries, relative to the top window.
Felipe Lemebab851c2017-02-03 18:45:08 -0800148 * @param gainFocus whether focus was gained or lost.
149 */
Felipe Leme7f60d352017-02-07 17:14:52 -0800150 public void virtualFocusChanged(View parent, int childId, Rect bounds, boolean gainFocus) {
Svet Ganov782043c2017-02-11 00:52:02 +0000151 ensureServiceClientAddedIfNeeded();
152
153 if (!mEnabled) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800154 return;
155 }
156
Felipe Lemebab851c2017-02-03 18:45:08 -0800157 final AutoFillId id = getAutoFillId(parent, childId);
Svet Ganov782043c2017-02-11 00:52:02 +0000158
159 if (!mHasSession) {
160 if (gainFocus) {
161 // Starts new session.
162 startSession(id, bounds, null);
163 }
164 } else {
165 // Update focus on existing session.
166 updateSession(id, bounds, null, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
167 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800168 }
169
170 /**
171 * Called to indicate the value of an auto-fillable {@link View} changed.
172 *
173 * @param view view whose focus changed.
174 */
175 public void valueChanged(View view) {
Svet Ganov782043c2017-02-11 00:52:02 +0000176 if (!mEnabled || !mHasSession) {
177 return;
178 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800179
180 final AutoFillId id = getAutoFillId(view);
181 final AutoFillValue value = view.getAutoFillValue();
182 updateSession(id, null, value, FLAG_VALUE_CHANGED);
183 }
184
185
186 /**
187 * Called to indicate the value of an auto-fillable virtual {@link View} changed.
188 *
189 * @param parent parent view whose value changed.
190 * @param childId id identifying the virtual child inside the parent view.
191 * @param value new value of the child.
192 */
193 public void virtualValueChanged(View parent, int childId, AutoFillValue value) {
Svet Ganov782043c2017-02-11 00:52:02 +0000194 if (!mEnabled || !mHasSession) {
195 return;
196 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800197
198 final AutoFillId id = getAutoFillId(parent, childId);
199 updateSession(id, null, value, FLAG_VALUE_CHANGED);
200 }
201
202 /**
203 * Called to indicate the current auto-fill context should be reset.
204 *
205 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
206 * call this method after the form is submitted and another page is rendered.
207 */
208 public void reset() {
Svet Ganov782043c2017-02-11 00:52:02 +0000209 if (!mEnabled && !mHasSession) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800210 return;
211 }
Svet Ganov782043c2017-02-11 00:52:02 +0000212
213 finishSession();
Felipe Leme0200d9e2017-01-24 15:10:26 -0800214 }
215
Svet Ganov782043c2017-02-11 00:52:02 +0000216 private AutoFillClient getClient() {
Svet Ganov17db9dc2017-02-21 19:54:31 -0800217 if (mContext instanceof AutoFillClient) {
218 return (AutoFillClient) mContext;
Svet Ganov782043c2017-02-11 00:52:02 +0000219 }
Svet Ganov17db9dc2017-02-21 19:54:31 -0800220 return null;
Svet Ganov782043c2017-02-11 00:52:02 +0000221 }
222
223 /** @hide */
224 public void onAuthenticationResult(Intent data) {
Felipe Lemed633f072017-02-14 10:17:17 -0800225 // TODO(b/33197203): the result code is being ignored, so this method is not reliably
226 // handling the cases where it's not RESULT_OK: it works fine if the service does not
227 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
228 // service set the extra and returned RESULT_CANCELED...
229
230 if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data);
231
Svet Ganov782043c2017-02-11 00:52:02 +0000232 if (data == null) {
233 return;
234 }
Felipe Lemed633f072017-02-14 10:17:17 -0800235 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
236 final Bundle responseData = new Bundle();
Svet Ganov782043c2017-02-11 00:52:02 +0000237 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
238 try {
239 mService.setAuthenticationResult(responseData,
240 mContext.getActivityToken(), mContext.getUserId());
241 } catch (RemoteException e) {
242 Log.e(TAG, "Error delivering authentication result", e);
243 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800244 }
245
Felipe Leme0200d9e2017-01-24 15:10:26 -0800246 private AutoFillId getAutoFillId(View view) {
247 return new AutoFillId(view.getAccessibilityViewId());
248 }
249
Felipe Lemebab851c2017-02-03 18:45:08 -0800250 private AutoFillId getAutoFillId(View parent, int childId) {
251 return new AutoFillId(parent.getAccessibilityViewId(), childId);
252 }
253
254 private void startSession(AutoFillId id, Rect bounds, AutoFillValue value) {
Svet Ganov782043c2017-02-11 00:52:02 +0000255 if (DEBUG) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800256 Log.v(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value);
257 }
Felipe Lemebab851c2017-02-03 18:45:08 -0800258 try {
Svet Ganov782043c2017-02-11 00:52:02 +0000259 mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(),
260 id, bounds, value, mContext.getUserId());
Svet Ganov17db9dc2017-02-21 19:54:31 -0800261 AutoFillClient client = getClient();
262 if (client != null) {
263 client.resetableStateAvailable();
264 }
Svet Ganov782043c2017-02-11 00:52:02 +0000265 mHasSession = true;
Svet Ganov782043c2017-02-11 00:52:02 +0000266 } catch (RemoteException e) {
267 throw e.rethrowFromSystemServer();
268 }
269 }
270
271 private void finishSession() {
272 if (DEBUG) {
273 Log.v(TAG, "finishSession()");
274 }
275 mHasSession = false;
276 try {
277 mService.finishSession(mContext.getActivityToken(), mContext.getUserId());
Felipe Lemebab851c2017-02-03 18:45:08 -0800278 } catch (RemoteException e) {
279 throw e.rethrowFromSystemServer();
280 }
281 }
282
283 private void updateSession(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
Svet Ganov782043c2017-02-11 00:52:02 +0000284 if (DEBUG) {
Felipe Lemebab851c2017-02-03 18:45:08 -0800285 Log.v(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value
286 + ", flags=" + flags);
287 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800288 try {
Svet Ganov782043c2017-02-11 00:52:02 +0000289 mService.updateSession(mContext.getActivityToken(), id, bounds, value, flags,
290 mContext.getUserId());
Felipe Leme3461d3c2017-01-19 08:54:55 -0800291 } catch (RemoteException e) {
292 throw e.rethrowFromSystemServer();
293 }
294 }
Svet Ganov782043c2017-02-11 00:52:02 +0000295
296 private void ensureServiceClientAddedIfNeeded() {
297 if (getClient() == null) {
298 return;
299 }
300 if (mServiceClient == null) {
301 mServiceClient = new AutoFillManagerClient(this);
302 try {
303 mEnabled = mService.addClient(mServiceClient, mContext.getUserId());
304 } catch (RemoteException e) {
305 throw e.rethrowFromSystemServer();
306 }
307 }
308 }
309
310 private static final class AutoFillManagerClient extends IAutoFillManagerClient.Stub {
311 private final WeakReference<AutoFillManager> mAutoFillManager;
312
313 AutoFillManagerClient(AutoFillManager autoFillManager) {
314 mAutoFillManager = new WeakReference<>(autoFillManager);
315 }
316
317 @Override
318 public void setState(boolean enabled) {
319 final AutoFillManager autoFillManager = mAutoFillManager.get();
320 if (autoFillManager != null) {
321 autoFillManager.mContext.getMainThreadHandler().post(() ->
322 autoFillManager.mEnabled = enabled);
323 }
324 }
325
326 @Override
327 public void autoFill(List<AutoFillId> ids, List<AutoFillValue> values) {
328 // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
329 // dataset.extras to service
330 final AutoFillManager autoFillManager = mAutoFillManager.get();
331 if (autoFillManager != null) {
332 autoFillManager.mContext.getMainThreadHandler().post(() -> {
333 if (autoFillManager.getClient() != null) {
334 autoFillManager.getClient().autoFill(ids, values);
335 }
336 });
337 }
338 }
339
340 @Override
341 public void authenticate(IntentSender intent, Intent fillInIntent) {
342 final AutoFillManager autoFillManager = mAutoFillManager.get();
343 if (autoFillManager != null) {
344 autoFillManager.mContext.getMainThreadHandler().post(() -> {
345 if (autoFillManager.getClient() != null) {
346 autoFillManager.getClient().authenticate(intent, fillInIntent);
347 }
348 });
349 }
350 }
351 }
Felipe Leme3461d3c2017-01-19 08:54:55 -0800352}