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 
39 /**
40  * A service that calculates field classification scores.
41  *
42  * <p>A field classification score is a {@code float} representing how well an
43  * {@link AutofillValue} filled matches a expected value predicted by an autofill service
44  * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
45  *
46  * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must provide
47  * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
48  * but it could provide more (in which case the algorithm name should be specified by the caller
49  * when calculating the scores).
50  *
51  * {@hide}
52  */
53 @SystemApi
54 public abstract class AutofillFieldClassificationService extends Service {
55 
56     private static final String TAG = "AutofillFieldClassificationService";
57 
58     /**
59      * The {@link Intent} action that must be declared as handled by a service
60      * in its manifest for the system to recognize it as a quota providing service.
61      */
62     public static final String SERVICE_INTERFACE =
63             "android.service.autofill.AutofillFieldClassificationService";
64 
65     /**
66      * Manifest metadata key for the resource string containing the name of the default field
67      * classification algorithm.
68      */
69     public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM =
70             "android.autofill.field_classification.default_algorithm";
71     /**
72      * Manifest metadata key for the resource string array containing the names of all field
73      * classification algorithms provided by the service.
74      */
75     public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
76             "android.autofill.field_classification.available_algorithms";
77 
78 
79     /** {@hide} **/
80     public static final String EXTRA_SCORES = "scores";
81 
82     private AutofillFieldClassificationServiceWrapper mWrapper;
83 
getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs, List<AutofillValue> actualValues, String[] userDataValues)84     private void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
85             List<AutofillValue> actualValues, String[] userDataValues) {
86         final Bundle data = new Bundle();
87         final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues,
88                 Arrays.asList(userDataValues));
89         if (scores != null) {
90             data.putParcelable(EXTRA_SCORES, new Scores(scores));
91         }
92         callback.sendResult(data);
93     }
94 
95     private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
96 
97     /** @hide */
AutofillFieldClassificationService()98     public AutofillFieldClassificationService() {
99 
100     }
101 
102     @Override
onCreate()103     public void onCreate() {
104         super.onCreate();
105         mWrapper = new AutofillFieldClassificationServiceWrapper();
106     }
107 
108     @Override
onBind(Intent intent)109     public IBinder onBind(Intent intent) {
110         return mWrapper;
111     }
112 
113     /**
114      * Calculates field classification scores in a batch.
115      *
116      * <p>A field classification score is a {@code float} representing how well an
117      * {@link AutofillValue} filled matches a expected value predicted by an autofill service
118      * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
119      *
120      * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
121      * provide at least one default algorithm (which is used when the algorithm is not specified
122      * or is invalid), but it could provide more (in which case the algorithm name should be
123      * specified by the caller when calculating the scores).
124      *
125      * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
126      * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
127      *
128      * <pre>
129      * service.onGetScores("EXACT_MATCH", null,
130      *   Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
131      *   Arrays.asList("email1", "phone1"));
132      * </pre>
133      *
134      * <p>Returns:
135      *
136      * <pre>
137      * [
138      *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
139      *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
140      * ];
141      * </pre>
142      *
143      * <p>If the same algorithm allows the caller to specify whether the comparisons should be
144      * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
145      *
146      * <pre>
147      * Bundle algorithmOptions = new Bundle();
148      * algorithmOptions.putBoolean("case_sensitive", false);
149      *
150      * service.onGetScores("EXACT_MATCH", algorithmOptions,
151      *   Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
152      *   Arrays.asList("email1", "phone1"));
153      * </pre>
154      *
155      * <p>Returns:
156      *
157      * <pre>
158      * [
159      *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
160      *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
161      * ];
162      * </pre>
163      *
164      * @param algorithm name of the algorithm to be used to calculate the scores. If invalid or
165      * {@code null}, the default algorithm is used instead.
166      * @param algorithmOptions optional arguments to be passed to the algorithm.
167      * @param actualValues values entered by the user.
168      * @param userDataValues values predicted from the user data.
169      * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
170      *
171      * {@hide}
172      */
173     @Nullable
174     @SystemApi
onGetScores(@ullable String algorithm, @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues)175     public float[][] onGetScores(@Nullable String algorithm,
176             @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
177             @NonNull List<String> userDataValues) {
178         Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScore()");
179         return null;
180     }
181 
182     private final class AutofillFieldClassificationServiceWrapper
183             extends IAutofillFieldClassificationService.Stub {
184         @Override
getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs, List<AutofillValue> actualValues, String[] userDataValues)185         public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
186                 List<AutofillValue> actualValues, String[] userDataValues)
187                         throws RemoteException {
188             mHandler.sendMessage(obtainMessage(
189                     AutofillFieldClassificationService::getScores,
190                     AutofillFieldClassificationService.this,
191                     callback, algorithmName, algorithmArgs, actualValues, userDataValues));
192         }
193     }
194 
195     /**
196      * Helper class used to encapsulate a float[][] in a Parcelable.
197      *
198      * {@hide}
199      */
200     public static final class Scores implements Parcelable {
201         @NonNull
202         public final float[][] scores;
203 
Scores(Parcel parcel)204         private Scores(Parcel parcel) {
205             final int size1 = parcel.readInt();
206             final int size2 = parcel.readInt();
207             scores = new float[size1][size2];
208             for (int i = 0; i < size1; i++) {
209                 for (int j = 0; j < size2; j++) {
210                     scores[i][j] = parcel.readFloat();
211                 }
212             }
213         }
214 
Scores(@onNull float[][] scores)215         private Scores(@NonNull float[][] scores) {
216             this.scores = scores;
217         }
218 
219         @Override
toString()220         public String toString() {
221             final int size1 = scores.length;
222             final int size2 = size1 > 0 ? scores[0].length : 0;
223             final StringBuilder builder = new StringBuilder("Scores [")
224                     .append(size1).append("x").append(size2).append("] ");
225             for (int i = 0; i < size1; i++) {
226                 builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' ');
227             }
228             return builder.toString();
229         }
230 
231         @Override
describeContents()232         public int describeContents() {
233             return 0;
234         }
235 
236         @Override
writeToParcel(Parcel parcel, int flags)237         public void writeToParcel(Parcel parcel, int flags) {
238             int size1 = scores.length;
239             int size2 = scores[0].length;
240             parcel.writeInt(size1);
241             parcel.writeInt(size2);
242             for (int i = 0; i < size1; i++) {
243                 for (int j = 0; j < size2; j++) {
244                     parcel.writeFloat(scores[i][j]);
245                 }
246             }
247         }
248 
249         public static final Creator<Scores> CREATOR = new Creator<Scores>() {
250             @Override
251             public Scores createFromParcel(Parcel parcel) {
252                 return new Scores(parcel);
253             }
254 
255             @Override
256             public Scores[] newArray(int size) {
257                 return new Scores[size];
258             }
259         };
260     }
261 }
262