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.app.Service;
24 import android.content.Intent;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.os.RemoteCallback;
32 import android.os.RemoteException;
33 import android.util.Log;
34 import android.view.autofill.AutofillValue;
35 
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Map;
39 
40 /**
41  * A service that calculates field classification scores.
42  *
43  * <p>A field classification score is a {@code float} representing how well an
44  * {@link AutofillValue} filled matches a expected value predicted by an autofill service
45  * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
46  *
47  * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must provide
48  * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
49  * but it could provide more (in which case the algorithm name should be specified by the caller
50  * when calculating the scores).
51  *
52  * {@hide}
53  */
54 @SystemApi
55 public abstract class AutofillFieldClassificationService extends Service {
56 
57     private static final String TAG = "AutofillFieldClassificationService";
58 
59     /**
60      * The {@link Intent} action that must be declared as handled by a service
61      * in its manifest for the system to recognize it as a quota providing service.
62      */
63     public static final String SERVICE_INTERFACE =
64             "android.service.autofill.AutofillFieldClassificationService";
65 
66     /**
67      * Manifest metadata key for the resource string containing the name of the default field
68      * classification algorithm.
69      */
70     public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM =
71             "android.autofill.field_classification.default_algorithm";
72     /**
73      * Manifest metadata key for the resource string array containing the names of all field
74      * classification algorithms provided by the service.
75      */
76     public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
77             "android.autofill.field_classification.available_algorithms";
78 
79     /**
80      * Field classification algorithm that computes the edit distance between two Strings.
81      *
82      * <p>Service implementation must provide this algorithm.</p>
83      */
84     public static final String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
85 
86     /**
87      * Field classification algorithm that computes whether the last four digits between two
88      * Strings match exactly.
89      *
90      * <p>Service implementation must provide this algorithm.</p>
91      */
92     public static final String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
93 
94     /**
95      * Field classification algorithm that compares a credit card string to known last four digits.
96      *
97      * <p>Service implementation must provide this algorithm.</p>
98      */
99     public static final String REQUIRED_ALGORITHM_CREDIT_CARD = "CREDIT_CARD";
100 
101     /** {@hide} **/
102     public static final String EXTRA_SCORES = "scores";
103 
104     private AutofillFieldClassificationServiceWrapper mWrapper;
105 
calculateScores(RemoteCallback callback, List<AutofillValue> actualValues, String[] userDataValues, String[] categoryIds, String defaultAlgorithm, Bundle defaultArgs, Map algorithms, Map args)106     private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
107             String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
108             Bundle defaultArgs, Map algorithms, Map args) {
109         final Bundle data = new Bundle();
110         final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues),
111                 Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args);
112         if (scores != null) {
113             data.putParcelable(EXTRA_SCORES, new Scores(scores));
114         }
115         callback.sendResult(data);
116     }
117 
118     private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
119 
120     /** @hide */
121     @SystemApi
AutofillFieldClassificationService()122     public AutofillFieldClassificationService() {
123     }
124 
125     @Override
onCreate()126     public void onCreate() {
127         super.onCreate();
128         mWrapper = new AutofillFieldClassificationServiceWrapper();
129     }
130 
131     @Override
onBind(Intent intent)132     public IBinder onBind(Intent intent) {
133         return mWrapper;
134     }
135 
136     /**
137      * Calculates field classification scores in a batch.
138      *
139      * <p>A field classification score is a {@code float} representing how well an
140      * {@link AutofillValue} filled matches a expected value predicted by an autofill service
141      * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
142      *
143      * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
144      * provide at least one default algorithm (which is used when the algorithm is not specified
145      * or is invalid), but it could provide more (in which case the algorithm name should be
146      * specified by the caller when calculating the scores).
147      *
148      * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
149      * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
150      *
151      * <pre>
152      * service.onGetScores("EXACT_MATCH", null,
153      *   Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
154      *   Arrays.asList("email1", "phone1"));
155      * </pre>
156      *
157      * <p>Returns:
158      *
159      * <pre>
160      * [
161      *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
162      *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
163      * ];
164      * </pre>
165      *
166      * <p>If the same algorithm allows the caller to specify whether the comparisons should be
167      * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
168      *
169      * <pre>
170      * Bundle algorithmOptions = new Bundle();
171      * algorithmOptions.putBoolean("case_sensitive", false);
172      *
173      * service.onGetScores("EXACT_MATCH", algorithmOptions,
174      *   Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
175      *   Arrays.asList("email1", "phone1"));
176      * </pre>
177      *
178      * <p>Returns:
179      *
180      * <pre>
181      * [
182      *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
183      *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
184      * ];
185      * </pre>
186      *
187      * @param algorithm name of the algorithm to be used to calculate the scores. If invalid or
188      * {@code null}, the default algorithm is used instead.
189      * @param algorithmOptions optional arguments to be passed to the algorithm.
190      * @param actualValues values entered by the user.
191      * @param userDataValues values predicted from the user data.
192      * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
193      *
194      * {@hide}
195      *
196      * @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead.
197      */
198     @Nullable
199     @SystemApi
200     @Deprecated
onGetScores(@ullable String algorithm, @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues)201     public float[][] onGetScores(@Nullable String algorithm,
202             @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
203             @NonNull List<String> userDataValues) {
204         Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScores()");
205         return null;
206     }
207 
208     /**
209      * Calculates field classification scores in a batch.
210      *
211      * <p>A field classification score is a {@code float} representing how well an
212      * {@link AutofillValue} matches a expected value predicted by an autofill service
213      * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
214      *
215      * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
216      * provide at least one default algorithm (which is used when the algorithm is not specified
217      * or is invalid), but it could provide more (in which case the algorithm name should be
218      * specified by the caller when calculating the scores).
219      *
220      * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
221      * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
222      *
223      * <pre>
224      * HashMap algorithms = new HashMap<>();
225      * algorithms.put("email", "EXACT_MATCH");
226      * algorithms.put("phone", "EXACT_MATCH");
227      *
228      * HashMap args = new HashMap<>();
229      * args.put("email", null);
230      * args.put("phone", null);
231      *
232      * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
233      * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
234      * Array.asList("email", "phone"), algorithms, args);
235      * </pre>
236      *
237      * <p>Returns:
238      *
239      * <pre>
240      * [
241      *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
242      *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
243      * ];
244      * </pre>
245      *
246      * <p>If the same algorithm allows the caller to specify whether the comparisons should be
247      * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
248      *
249      * <pre>
250      * Bundle algorithmOptions = new Bundle();
251      * algorithmOptions.putBoolean("case_sensitive", false);
252      * args.put("phone", algorithmOptions);
253      *
254      * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
255      * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
256      * Array.asList("email", "phone"), algorithms, args);
257      * </pre>
258      *
259      * <p>Returns:
260      *
261      * <pre>
262      * [
263      *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
264      *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
265      * ];
266      * </pre>
267      *
268      * @param actualValues values entered by the user.
269      * @param userDataValues values predicted from the user data.
270      * @param categoryIds category Ids correspoinding to userDataValues
271      * @param defaultAlgorithm default field classification algorithm
272      * @param algorithms array of field classification algorithms
273      * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
274      *
275      * {@hide}
276      */
277     @Nullable
278     @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)279     public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
280             @NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
281             @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
282             @Nullable Map algorithms, @Nullable Map args) {
283         Log.e(TAG, "service implementation (" + getClass()
284                 + " does not implement onCalculateScore()");
285         return null;
286     }
287 
288     private final class AutofillFieldClassificationServiceWrapper
289             extends IAutofillFieldClassificationService.Stub {
290         @Override
calculateScores(RemoteCallback callback, List<AutofillValue> actualValues, String[] userDataValues, String[] categoryIds, String defaultAlgorithm, Bundle defaultArgs, Map algorithms, Map args)291         public void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
292                 String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
293                 Bundle defaultArgs, Map algorithms, Map args)
294                 throws RemoteException {
295             mHandler.sendMessage(obtainMessage(
296                     AutofillFieldClassificationService::calculateScores,
297                     AutofillFieldClassificationService.this,
298                     callback, actualValues, userDataValues, categoryIds, defaultAlgorithm,
299                     defaultArgs, algorithms, args));
300         }
301     }
302 
303     /**
304      * Helper class used to encapsulate a float[][] in a Parcelable.
305      *
306      * {@hide}
307      */
308     public static final class Scores implements Parcelable {
309         @NonNull
310         public final float[][] scores;
311 
Scores(Parcel parcel)312         private Scores(Parcel parcel) {
313             final int size1 = parcel.readInt();
314             final int size2 = parcel.readInt();
315             scores = new float[size1][size2];
316             for (int i = 0; i < size1; i++) {
317                 for (int j = 0; j < size2; j++) {
318                     scores[i][j] = parcel.readFloat();
319                 }
320             }
321         }
322 
Scores(@onNull float[][] scores)323         private Scores(@NonNull float[][] scores) {
324             this.scores = scores;
325         }
326 
327         @Override
toString()328         public String toString() {
329             final int size1 = scores.length;
330             final int size2 = size1 > 0 ? scores[0].length : 0;
331             final StringBuilder builder = new StringBuilder("Scores [")
332                     .append(size1).append("x").append(size2).append("] ");
333             for (int i = 0; i < size1; i++) {
334                 builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' ');
335             }
336             return builder.toString();
337         }
338 
339         @Override
describeContents()340         public int describeContents() {
341             return 0;
342         }
343 
344         @Override
writeToParcel(Parcel parcel, int flags)345         public void writeToParcel(Parcel parcel, int flags) {
346             int size1 = scores.length;
347             int size2 = scores[0].length;
348             parcel.writeInt(size1);
349             parcel.writeInt(size2);
350             for (int i = 0; i < size1; i++) {
351                 for (int j = 0; j < size2; j++) {
352                     parcel.writeFloat(scores[i][j]);
353                 }
354             }
355         }
356 
357         public static final @android.annotation.NonNull Creator<Scores> CREATOR = new Creator<Scores>() {
358             @Override
359             public Scores createFromParcel(Parcel parcel) {
360                 return new Scores(parcel);
361             }
362 
363             @Override
364             public Scores[] newArray(int size) {
365                 return new Scores[size];
366             }
367         };
368     }
369 }
370