blob: 1ef6100fe5a2ac406b5e2902247667c428990e5f [file] [log] [blame]
Felipe Lemebc055b02018-01-05 17:04:10 -08001/*
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 */
16package android.service.autofill;
17
Felipe Lemebc055b02018-01-05 17:04:10 -080018import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.annotation.SystemApi;
21import android.app.Service;
22import android.content.Intent;
23import android.os.Bundle;
24import android.os.IBinder;
25import android.os.Looper;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.os.RemoteCallback;
29import android.os.RemoteException;
30import android.util.Log;
31import android.view.autofill.AutofillValue;
32
33import com.android.internal.os.HandlerCaller;
34import com.android.internal.os.SomeArgs;
35
36import java.util.Arrays;
37import 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 specifiied by the caller
49 * when calculating the scores).
50 *
51 * {@hide}
52 */
53@SystemApi
54public abstract class AutofillFieldClassificationService extends Service {
55
56 private static final String TAG = "AutofillFieldClassificationService";
57
Felipe Lemee4ac7402018-01-16 19:37:00 -080058 private static final int MSG_GET_SCORES = 1;
Felipe Lemebc055b02018-01-05 17:04:10 -080059
60 /**
61 * The {@link Intent} action that must be declared as handled by a service
62 * in its manifest for the system to recognize it as a quota providing service.
63 */
64 public static final String SERVICE_INTERFACE =
65 "android.service.autofill.AutofillFieldClassificationService";
66
Felipe Lemed11a6622018-01-18 13:38:24 -080067 /**
68 * Manifest metadata key for the resource string containing the name of the default field
69 * classification algorithm.
70 */
71 public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM =
72 "android.autofill.field_classification.default_algorithm";
73 /**
74 * Manifest metadata key for the resource string array containing the names of all field
75 * classification algorithms provided by the service.
76 */
77 public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
78 "android.autofill.field_classification.available_algorithms";
79
80
Felipe Lemebc055b02018-01-05 17:04:10 -080081 /** {@hide} **/
82 public static final String EXTRA_SCORES = "scores";
83
84 private AutofillFieldClassificationServiceWrapper mWrapper;
85
86 private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
87 final int action = msg.what;
88 final Bundle data = new Bundle();
89 final RemoteCallback callback;
90 switch (action) {
Felipe Lemebc055b02018-01-05 17:04:10 -080091 case MSG_GET_SCORES:
92 final SomeArgs args = (SomeArgs) msg.obj;
93 callback = (RemoteCallback) args.arg1;
94 final String algorithmName = (String) args.arg2;
95 final Bundle algorithmArgs = (Bundle) args.arg3;
96 @SuppressWarnings("unchecked")
97 final List<AutofillValue> actualValues = ((List<AutofillValue>) args.arg4);
98 @SuppressWarnings("unchecked")
99 final String[] userDataValues = (String[]) args.arg5;
Felipe Lemed11a6622018-01-18 13:38:24 -0800100 final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues,
Felipe Lemebc055b02018-01-05 17:04:10 -0800101 Arrays.asList(userDataValues));
Felipe Leme029f1012018-01-22 11:53:44 -0800102 if (scores != null) {
103 data.putParcelable(EXTRA_SCORES, new Scores(scores));
104 }
Felipe Lemebc055b02018-01-05 17:04:10 -0800105 break;
106 default:
107 Log.w(TAG, "Handling unknown message: " + action);
108 return;
109 }
110 callback.sendResult(data);
111 };
112
113 private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
114 mHandlerCallback, true);
115
116 /** @hide */
117 public AutofillFieldClassificationService() {
118
119 }
120
121 @Override
122 public void onCreate() {
123 super.onCreate();
124 mWrapper = new AutofillFieldClassificationServiceWrapper();
125 }
126
127 @Override
128 public IBinder onBind(Intent intent) {
129 return mWrapper;
130 }
131
132 /**
Felipe Lemebc055b02018-01-05 17:04:10 -0800133 * Calculates field classification scores in a batch.
134 *
135 * <p>See {@link AutofillFieldClassificationService} for more info about field classification
136 * scores.
137 *
138 * @param algorithm name of the algorithm to be used to calculate the scores. If invalid, the
139 * default algorithm will be used instead.
140 * @param args optional arguments to be passed to the algorithm.
141 * @param actualValues values entered by the user.
142 * @param userDataValues values predicted from the user data.
Felipe Lemed11a6622018-01-18 13:38:24 -0800143 * @return the calculated scores, with the first dimension representing actual values and the
144 * second dimension values from {@link UserData}.
Felipe Lemebc055b02018-01-05 17:04:10 -0800145 *
146 * {@hide}
147 */
148 @Nullable
149 @SystemApi
Felipe Lemed11a6622018-01-18 13:38:24 -0800150 public float[][] onGetScores(@Nullable String algorithm,
Felipe Lemebc055b02018-01-05 17:04:10 -0800151 @Nullable Bundle args, @NonNull List<AutofillValue> actualValues,
152 @NonNull List<String> userDataValues) {
Felipe Leme029f1012018-01-22 11:53:44 -0800153 Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScore()");
154 return null;
Felipe Lemebc055b02018-01-05 17:04:10 -0800155 }
156
157 private final class AutofillFieldClassificationServiceWrapper
158 extends IAutofillFieldClassificationService.Stub {
Felipe Lemebc055b02018-01-05 17:04:10 -0800159 @Override
160 public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
161 List<AutofillValue> actualValues, String[] userDataValues)
162 throws RemoteException {
163 // TODO(b/70939974): refactor to use PooledLambda
164 mHandlerCaller.obtainMessageOOOOO(MSG_GET_SCORES, callback, algorithmName,
165 algorithmArgs, actualValues, userDataValues).sendToTarget();
166 }
167 }
168
Felipe Lemebc055b02018-01-05 17:04:10 -0800169 /**
Felipe Lemed11a6622018-01-18 13:38:24 -0800170 * Helper class used to encapsulate a float[][] in a Parcelable.
Felipe Lemebc055b02018-01-05 17:04:10 -0800171 *
172 * {@hide}
173 */
Felipe Lemebc055b02018-01-05 17:04:10 -0800174 public static final class Scores implements Parcelable {
Felipe Lemefe05a522018-01-23 15:57:49 -0800175 @NonNull
Felipe Lemed11a6622018-01-18 13:38:24 -0800176 public final float[][] scores;
Felipe Lemebc055b02018-01-05 17:04:10 -0800177
Felipe Lemed11a6622018-01-18 13:38:24 -0800178 private Scores(Parcel parcel) {
Felipe Lemebc055b02018-01-05 17:04:10 -0800179 final int size1 = parcel.readInt();
180 final int size2 = parcel.readInt();
Felipe Lemed11a6622018-01-18 13:38:24 -0800181 scores = new float[size1][size2];
Felipe Lemebc055b02018-01-05 17:04:10 -0800182 for (int i = 0; i < size1; i++) {
183 for (int j = 0; j < size2; j++) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800184 scores[i][j] = parcel.readFloat();
Felipe Lemebc055b02018-01-05 17:04:10 -0800185 }
186 }
187 }
188
Felipe Lemefe05a522018-01-23 15:57:49 -0800189 private Scores(@NonNull float[][] scores) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800190 this.scores = scores;
Felipe Lemebc055b02018-01-05 17:04:10 -0800191 }
192
193 @Override
Felipe Lemefe05a522018-01-23 15:57:49 -0800194 public String toString() {
195 final int size1 = scores.length;
196 final int size2 = size1 > 0 ? scores[0].length : 0;
197 final StringBuilder builder = new StringBuilder("Scores [")
198 .append(size1).append("x").append(size2).append("] ");
199 for (int i = 0; i < size1; i++) {
200 builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' ');
201 }
202 return builder.toString();
203 }
204
205 @Override
Felipe Lemebc055b02018-01-05 17:04:10 -0800206 public int describeContents() {
207 return 0;
208 }
209
210 @Override
211 public void writeToParcel(Parcel parcel, int flags) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800212 int size1 = scores.length;
213 int size2 = scores[0].length;
Felipe Lemebc055b02018-01-05 17:04:10 -0800214 parcel.writeInt(size1);
215 parcel.writeInt(size2);
216 for (int i = 0; i < size1; i++) {
217 for (int j = 0; j < size2; j++) {
Felipe Lemed11a6622018-01-18 13:38:24 -0800218 parcel.writeFloat(scores[i][j]);
Felipe Lemebc055b02018-01-05 17:04:10 -0800219 }
220 }
221 }
222
223 public static final Creator<Scores> CREATOR = new Creator<Scores>() {
Felipe Lemebc055b02018-01-05 17:04:10 -0800224 @Override
225 public Scores createFromParcel(Parcel parcel) {
226 return new Scores(parcel);
227 }
228
229 @Override
230 public Scores[] newArray(int size) {
231 return new Scores[size];
232 }
Felipe Lemebc055b02018-01-05 17:04:10 -0800233 };
234 }
235}