1 /*
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  */
16 package android.service.autofill;
17 
18 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.annotation.TestApi;
24 import android.app.Service;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.os.RemoteCallback;
33 import android.os.RemoteException;
34 import android.util.Log;
35 import android.view.autofill.AutofillValue;
36 
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.Map;
40 
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
46  * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
47  *
48  * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must provide
49  * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
50  * but it could provide more (in which case the algorithm name should be specified by the caller
51  * when calculating the scores).
52  *
53  * {@hide}
54  */
55 @SystemApi
56 @TestApi
57 public abstract class AutofillFieldClassificationService extends Service {
58 
59     private static final String TAG = "AutofillFieldClassificationService";
60 
61     /**
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 
68     /**
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 
81     /**
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";
95 
96     /** {@hide} **/
97     public static final String EXTRA_SCORES = "scores";
98 
99     private AutofillFieldClassificationServiceWrapper mWrapper;
100 
calculateScores(RemoteCallback callback, List<AutofillValue> actualValues, String[] userDataValues, String[] categoryIds, String defaultAlgorithm, Bundle defaultArgs, Map algorithms, Map args)101     private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
102             String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
103             Bundle defaultArgs, Map algorithms, Map args) {
104         final Bundle data = new Bundle();
105         final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues),
106                 Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args);
107         if (scores != null) {
108             data.putParcelable(EXTRA_SCORES, new Scores(scores));
109         }
110         callback.sendResult(data);
111     }
112 
113     private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
114 
115     /** @hide */
AutofillFieldClassificationService()116     public AutofillFieldClassificationService() {
117 
118     }
119 
120     @Override
onCreate()121     public void onCreate() {
122         super.onCreate();
123         mWrapper = new AutofillFieldClassificationServiceWrapper();
124     }
125 
126     @Override
onBind(Intent intent)127     public IBinder onBind(Intent intent) {
128         return mWrapper;
129     }
130 
131     /**
132      * Calculates field classification scores in a batch.
133      *
134      * <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
136      * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
137      *
138      * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
139      * 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
141      * specified by the caller when calculating the scores).
142      *
143      * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
144      * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
145      *
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.
185      * @param actualValues values entered by the user.
186      * @param userDataValues values predicted from the user data.
187      * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
188      *
189      * {@hide}
190      *
191      * @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead.
192      */
193     @Nullable
194     @SystemApi
195     @Deprecated
onGetScores(@ullable String algorithm, @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues)196     public float[][] onGetScores(@Nullable String algorithm,
197             @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
198             @NonNull List<String> userDataValues) {
199         Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScores()");
200         return null;
201     }
202 
203     /**
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
onCalculateScores(@onNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues, @NonNull List<String> categoryIds, @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, @Nullable Map algorithms, @Nullable Map args)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 
283     private final class AutofillFieldClassificationServiceWrapper
284             extends IAutofillFieldClassificationService.Stub {
285         @Override
calculateScores(RemoteCallback callback, List<AutofillValue> actualValues, String[] userDataValues, String[] categoryIds, String defaultAlgorithm, Bundle defaultArgs, Map algorithms, Map args)286         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 {
290             mHandler.sendMessage(obtainMessage(
291                     AutofillFieldClassificationService::calculateScores,
292                     AutofillFieldClassificationService.this,
293                     callback, actualValues, userDataValues, categoryIds, defaultAlgorithm,
294                     defaultArgs, algorithms, args));
295         }
296     }
297 
298     /**
299      * Helper class used to encapsulate a float[][] in a Parcelable.
300      *
301      * {@hide}
302      */
303     public static final class Scores implements Parcelable {
304         @NonNull
305         public final float[][] scores;
306 
Scores(Parcel parcel)307         private Scores(Parcel parcel) {
308             final int size1 = parcel.readInt();
309             final int size2 = parcel.readInt();
310             scores = new float[size1][size2];
311             for (int i = 0; i < size1; i++) {
312                 for (int j = 0; j < size2; j++) {
313                     scores[i][j] = parcel.readFloat();
314                 }
315             }
316         }
317 
Scores(@onNull float[][] scores)318         private Scores(@NonNull float[][] scores) {
319             this.scores = scores;
320         }
321 
322         @Override
toString()323         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
describeContents()335         public int describeContents() {
336             return 0;
337         }
338 
339         @Override
writeToParcel(Parcel parcel, int flags)340         public void writeToParcel(Parcel parcel, int flags) {
341             int size1 = scores.length;
342             int size2 = scores[0].length;
343             parcel.writeInt(size1);
344             parcel.writeInt(size2);
345             for (int i = 0; i < size1; i++) {
346                 for (int j = 0; j < size2; j++) {
347                     parcel.writeFloat(scores[i][j]);
348                 }
349             }
350         }
351 
352         public static final @android.annotation.NonNull Creator<Scores> CREATOR = new Creator<Scores>() {
353             @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             }
362         };
363     }
364 }
365