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 * —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—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 * —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—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 * —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—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