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