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