blob: 5f64755f73379256fa40b146be84f31a4fa1e898 [file] [log] [blame]
Felipe Lemebc055b02018-01-05 17:04:10 -08001/*
2 * Copyright (C) 2018 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 */
16package android.service.autofill;
17
Eugene Susla9f1921f2018-02-12 14:33:15 -080018import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
19
Felipe Lemebc055b02018-01-05 17:04:10 -080020import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.SystemApi;
Adam He71d53e02018-11-06 14:03:26 -080023import android.annotation.TestApi;
Felipe Lemebc055b02018-01-05 17:04:10 -080024import android.app.Service;
25import android.content.Intent;
26import android.os.Bundle;
Eugene Susla9f1921f2018-02-12 14:33:15 -080027import android.os.Handler;
Felipe Lemebc055b02018-01-05 17:04:10 -080028import android.os.IBinder;
29import android.os.Looper;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.os.RemoteCallback;
33import android.os.RemoteException;
34import android.util.Log;
35import android.view.autofill.AutofillValue;
36
Felipe Lemebc055b02018-01-05 17:04:10 -080037import java.util.Arrays;
38import java.util.List;
Adam He71d53e02018-11-06 14:03:26 -080039import java.util.Map;
Felipe Lemebc055b02018-01-05 17:04:10 -080040
41/**
42 * A service that calculates field classification scores.
43 *
44 * <p>A field classification score is a {@code float} representing how well an
45 * {@link AutofillValue} filled matches a expected value predicted by an autofill service
Felipe Leme8ce43a52018-03-29 13:01:49 -070046 * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
Felipe Lemebc055b02018-01-05 17:04:10 -080047 *
Felipe Leme8ce43a52018-03-29 13:01:49 -070048 * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must provide
Felipe Lemebc055b02018-01-05 17:04:10 -080049 * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
Felipe Leme8ce43a52018-03-29 13:01:49 -070050 * but it could provide more (in which case the algorithm name should be specified by the caller
Felipe Lemebc055b02018-01-05 17:04:10 -080051 * when calculating the scores).
52 *
53 * {@hide}
54 */
55@SystemApi
Adam He71d53e02018-11-06 14:03:26 -080056@TestApi
Felipe Lemebc055b02018-01-05 17:04:10 -080057public abstract class AutofillFieldClassificationService extends Service {
58
59 private static final String TAG = "AutofillFieldClassificationService";
60
Felipe Lemebc055b02018-01-05 17:04:10 -080061 /**
62 * The {@link Intent} action that must be declared as handled by a service
63 * in its manifest for the system to recognize it as a quota providing service.
64 */
65 public static final String SERVICE_INTERFACE =
66 "android.service.autofill.AutofillFieldClassificationService";
67
Felipe Lemed11a6622018-01-18 13:38:24 -080068 /**
69 * Manifest metadata key for the resource string containing the name of the default field
70 * classification algorithm.
71 */
72 public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM =
73 "android.autofill.field_classification.default_algorithm";
74 /**
75 * Manifest metadata key for the resource string array containing the names of all field
76 * classification algorithms provided by the service.
77 */
78 public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
79 "android.autofill.field_classification.available_algorithms";
80
Adam He71d53e02018-11-06 14:03:26 -080081 /**
82 * Field classification algorithm that computes the edit distance between two Strings.
83 *
84 * <p>Service implementation must provide this algorithm.</p>
85 */
86 public static final String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
87
88 /**
89 * Field classification algorithm that computes whether the last four digits between two
90 * Strings match exactly.
91 *
92 * <p>Service implementation must provide this algorithm.</p>
93 */
94 public static final String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
Felipe Lemed11a6622018-01-18 13:38:24 -080095
Felipe Lemebc055b02018-01-05 17:04:10 -080096 /** {@hide} **/
97 public static final String EXTRA_SCORES = "scores";
98
99 private AutofillFieldClassificationServiceWrapper mWrapper;
100
Adam He71d53e02018-11-06 14:03:26 -0800101 private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
102 String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
103 Bundle defaultArgs, Map algorithms, Map args) {
Felipe Lemebc055b02018-01-05 17:04:10 -0800104 final Bundle data = new Bundle();
Adam He71d53e02018-11-06 14:03:26 -0800105 final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues),
106 Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args);
Eugene Susla9f1921f2018-02-12 14:33:15 -0800107 if (scores != null) {
108 data.putParcelable(EXTRA_SCORES, new Scores(scores));
Felipe Lemebc055b02018-01-05 17:04:10 -0800109 }
110 callback.sendResult(data);
Eugene Susla9f1921f2018-02-12 14:33:15 -0800111 }
Felipe Lemebc055b02018-01-05 17:04:10 -0800112
Eugene Susla9f1921f2018-02-12 14:33:15 -0800113 private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
Felipe Lemebc055b02018-01-05 17:04:10 -0800114
115 /** @hide */
116 public AutofillFieldClassificationService() {
117
118 }
119
120 @Override
121 public void onCreate() {
122 super.onCreate();
123 mWrapper = new AutofillFieldClassificationServiceWrapper();
124 }
125
126 @Override
127 public IBinder onBind(Intent intent) {
128 return mWrapper;
129 }
130
131 /**
Felipe Lemebc055b02018-01-05 17:04:10 -0800132 * Calculates field classification scores in a batch.
133 *
Felipe Leme20d30e52018-03-28 14:14:17 -0700134 * <p>A field classification score is a {@code float} representing how well an
135 * {@link AutofillValue} filled matches a expected value predicted by an autofill service
Felipe Leme8ce43a52018-03-29 13:01:49 -0700136 * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
Felipe Lemebc055b02018-01-05 17:04:10 -0800137 *
Felipe Leme8ce43a52018-03-29 13:01:49 -0700138 * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
Felipe Leme20d30e52018-03-28 14:14:17 -0700139 * provide at least one default algorithm (which is used when the algorithm is not specified
140 * or is invalid), but it could provide more (in which case the algorithm name should be
Felipe Leme8ce43a52018-03-29 13:01:49 -0700141 * specified by the caller when calculating the scores).
Felipe Leme20d30e52018-03-28 14:14:17 -0700142 *
143 * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
Felipe Leme8ce43a52018-03-29 13:01:49 -0700144 * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
Felipe Leme20d30e52018-03-28 14:14:17 -0700145 *
146 * <pre>
147 * service.onGetScores("EXACT_MATCH", null,
148 * Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
149 * Arrays.asList("email1", "phone1"));
150 * </pre>
151 *
152 * <p>Returns:
153 *
154 * <pre>
155 * [
156 * [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
157 * [0.0, 0.0] // "PHONE1" compared against ["email1", "phone1"]
158 * ];
159 * </pre>
160 *
161 * <p>If the same algorithm allows the caller to specify whether the comparisons should be
162 * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
163 *
164 * <pre>
165 * Bundle algorithmOptions = new Bundle();
166 * algorithmOptions.putBoolean("case_sensitive", false);
167 *
168 * service.onGetScores("EXACT_MATCH", algorithmOptions,
169 * Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
170 * Arrays.asList("email1", "phone1"));
171 * </pre>
172 *
173 * <p>Returns:
174 *
175 * <pre>
176 * [
177 * [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
178 * [0.0, 1.0] // "PHONE1" compared against ["email1", "phone1"]
179 * ];
180 * </pre>
181 *
182 * @param algorithm name of the algorithm to be used to calculate the scores. If invalid or
183 * {@code null}, the default algorithm is used instead.
184 * @param algorithmOptions optional arguments to be passed to the algorithm.
Felipe Lemebc055b02018-01-05 17:04:10 -0800185 * @param actualValues values entered by the user.
186 * @param userDataValues values predicted from the user data.
Felipe Leme20d30e52018-03-28 14:14:17 -0700187 * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
Felipe Lemebc055b02018-01-05 17:04:10 -0800188 *
189 * {@hide}
Adam He71d53e02018-11-06 14:03:26 -0800190 *
191 * @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead.
Felipe Lemebc055b02018-01-05 17:04:10 -0800192 */
193 @Nullable
194 @SystemApi
Adam He71d53e02018-11-06 14:03:26 -0800195 @Deprecated
Felipe Lemed11a6622018-01-18 13:38:24 -0800196 public float[][] onGetScores(@Nullable String algorithm,
Felipe Leme20d30e52018-03-28 14:14:17 -0700197 @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
Felipe Lemebc055b02018-01-05 17:04:10 -0800198 @NonNull List<String> userDataValues) {
Adam He2cc31462018-11-12 16:26:14 -0800199 Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScores()");
Felipe Leme029f1012018-01-22 11:53:44 -0800200 return null;
Felipe Lemebc055b02018-01-05 17:04:10 -0800201 }
202
Adam He71d53e02018-11-06 14:03:26 -0800203 /**
204 * Calculates field classification scores in a batch.
205 *
206 * <p>A field classification score is a {@code float} representing how well an
207 * {@link AutofillValue} matches a expected value predicted by an autofill service
208 * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
209 *
210 * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
211 * provide at least one default algorithm (which is used when the algorithm is not specified
212 * or is invalid), but it could provide more (in which case the algorithm name should be
213 * specified by the caller when calculating the scores).
214 *
215 * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
216 * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
217 *
218 * <pre>
219 * HashMap algorithms = new HashMap<>();
220 * algorithms.put("email", "EXACT_MATCH");
221 * algorithms.put("phone", "EXACT_MATCH");
222 *
223 * HashMap args = new HashMap<>();
224 * args.put("email", null);
225 * args.put("phone", null);
226 *
227 * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
228 * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
229 * Array.asList("email", "phone"), algorithms, args);
230 * </pre>
231 *
232 * <p>Returns:
233 *
234 * <pre>
235 * [
236 * [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
237 * [0.0, 0.0] // "PHONE1" compared against ["email1", "phone1"]
238 * ];
239 * </pre>
240 *
241 * <p>If the same algorithm allows the caller to specify whether the comparisons should be
242 * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
243 *
244 * <pre>
245 * Bundle algorithmOptions = new Bundle();
246 * algorithmOptions.putBoolean("case_sensitive", false);
247 * args.put("phone", algorithmOptions);
248 *
249 * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
250 * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
251 * Array.asList("email", "phone"), algorithms, args);
252 * </pre>
253 *
254 * <p>Returns:
255 *
256 * <pre>
257 * [
258 * [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
259 * [0.0, 1.0] // "PHONE1" compared against ["email1", "phone1"]
260 * ];
261 * </pre>
262 *
263 * @param actualValues values entered by the user.
264 * @param userDataValues values predicted from the user data.
265 * @param categoryIds category Ids correspoinding to userDataValues
266 * @param defaultAlgorithm default field classification algorithm
267 * @param algorithms array of field classification algorithms
268 * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
269 *
270 * {@hide}
271 */
272 @Nullable
273 @SystemApi
274 public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
275 @NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
276 @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
277 @Nullable Map algorithms, @Nullable Map args) {
278 Log.e(TAG, "service implementation (" + getClass()
279 + " does not implement onCalculateScore()");
280 return null;
281 }
282
Felipe Lemebc055b02018-01-05 17:04:10 -0800283 private final class AutofillFieldClassificationServiceWrapper
284 extends IAutofillFieldClassificationService.Stub {
Felipe Lemebc055b02018-01-05 17:04:10 -0800285 @Override
Adam He71d53e02018-11-06 14:03:26 -0800286 public void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
287 String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
288 Bundle defaultArgs, Map algorithms, Map args)
289 throws RemoteException {
Eugene Susla9f1921f2018-02-12 14:33:15 -0800290 mHandler.sendMessage(obtainMessage(
Adam He71d53e02018-11-06 14:03:26 -0800291 AutofillFieldClassificationService::calculateScores,
Eugene Susla9f1921f2018-02-12 14:33:15 -0800292 AutofillFieldClassificationService.this,
Adam He71d53e02018-11-06 14:03:26 -0800293 callback, actualValues, userDataValues, categoryIds, defaultAlgorithm,
294 defaultArgs, algorithms, args));
Felipe Lemebc055b02018-01-05 17:04:10 -0800295 }
296 }
297
Felipe Lemebc055b02018-01-05 17:04:10 -0800298 /**
Felipe Lemed11a6622018-01-18 13:38:24 -0800299 * Helper class used to encapsulate a float[][] in a Parcelable.
Felipe Lemebc055b02018-01-05 17:04:10 -0800300 *
301 * {@hide}
302 */
Felipe Lemebc055b02018-01-05 17:04:10 -0800303 public static final class Scores implements Parcelable {
Felipe Lemefe05a522018-01-23 15:57:49 -0800304 @NonNull
Felipe Lemed11a6622018-01-18 13:38:24 -0800305 public final float[][] scores;
Felipe Lemebc055b02018-01-05 17:04:10 -0800306
Felipe Lemed11a6622018-01-18 13:38:24 -0800307 private Scores(Parcel parcel) {
Felipe Lemebc055b02018-01-05 17:04:10 -0800308 final int size1 = parcel.readInt();
309 final int size2 = parcel.readInt();
Felipe Lemed11a6622018-01-18 13:38:24 -0800310 scores = new float[size1][size2];
Felipe Lemebc055b02018-01-05 17:04:10 -0800311 for (int i = 0; i < size1; i++) {
312 for (int j = 0; j < size2; j++) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800313 scores[i][j] = parcel.readFloat();
Felipe Lemebc055b02018-01-05 17:04:10 -0800314 }
315 }
316 }
317
Felipe Lemefe05a522018-01-23 15:57:49 -0800318 private Scores(@NonNull float[][] scores) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800319 this.scores = scores;
Felipe Lemebc055b02018-01-05 17:04:10 -0800320 }
321
322 @Override
Felipe Lemefe05a522018-01-23 15:57:49 -0800323 public String toString() {
324 final int size1 = scores.length;
325 final int size2 = size1 > 0 ? scores[0].length : 0;
326 final StringBuilder builder = new StringBuilder("Scores [")
327 .append(size1).append("x").append(size2).append("] ");
328 for (int i = 0; i < size1; i++) {
329 builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' ');
330 }
331 return builder.toString();
332 }
333
334 @Override
Felipe Lemebc055b02018-01-05 17:04:10 -0800335 public int describeContents() {
336 return 0;
337 }
338
339 @Override
340 public void writeToParcel(Parcel parcel, int flags) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800341 int size1 = scores.length;
342 int size2 = scores[0].length;
Felipe Lemebc055b02018-01-05 17:04:10 -0800343 parcel.writeInt(size1);
344 parcel.writeInt(size2);
345 for (int i = 0; i < size1; i++) {
346 for (int j = 0; j < size2; j++) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800347 parcel.writeFloat(scores[i][j]);
Felipe Lemebc055b02018-01-05 17:04:10 -0800348 }
349 }
350 }
351
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700352 public static final @android.annotation.NonNull Creator<Scores> CREATOR = new Creator<Scores>() {
Felipe Lemebc055b02018-01-05 17:04:10 -0800353 @Override
354 public Scores createFromParcel(Parcel parcel) {
355 return new Scores(parcel);
356 }
357
358 @Override
359 public Scores[] newArray(int size) {
360 return new Scores[size];
361 }
Felipe Lemebc055b02018-01-05 17:04:10 -0800362 };
363 }
364}