Home | History | Annotate | Download | only in autofill
      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 
     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 */
     98     public AutofillFieldClassificationService() {
     99 
    100     }
    101 
    102     @Override
    103     public void onCreate() {
    104         super.onCreate();
    105         mWrapper = new AutofillFieldClassificationServiceWrapper();
    106     }
    107 
    108     @Override
    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
    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
    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 
    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 
    215         private Scores(@NonNull float[][] scores) {
    216             this.scores = scores;
    217         }
    218 
    219         @Override
    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
    232         public int describeContents() {
    233             return 0;
    234         }
    235 
    236         @Override
    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