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