blob: a793e09e79f1777e5eee1834c34e603d87a7d903 [file] [log] [blame]
Felipe Leme452886a2017-11-27 13:09:13 -08001/*
2 * Copyright 2017 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 Lemefebb7332018-02-12 18:12:55 -080018import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
Felipe Leme452886a2017-11-27 13:09:13 -080019import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
20import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
21import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
22import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
23import static android.view.autofill.Helper.sDebug;
24
25import android.annotation.NonNull;
26import android.annotation.Nullable;
Adam He71d53e02018-11-06 14:03:26 -080027import android.annotation.TestApi;
Felipe Leme452886a2017-11-27 13:09:13 -080028import android.app.ActivityThread;
29import android.content.ContentResolver;
Felipe Leme27f45732017-12-22 09:05:22 -080030import android.os.Bundle;
Felipe Leme452886a2017-11-27 13:09:13 -080031import android.os.Parcel;
32import android.os.Parcelable;
33import android.provider.Settings;
Felipe Leme27f45732017-12-22 09:05:22 -080034import android.service.autofill.FieldClassification.Match;
Felipe Lemef0baef742018-01-26 14:39:39 -080035import android.text.TextUtils;
Adam He71d53e02018-11-06 14:03:26 -080036import android.util.ArrayMap;
Felipe Lemefebb7332018-02-12 18:12:55 -080037import android.util.ArraySet;
Felipe Leme452886a2017-11-27 13:09:13 -080038import android.util.Log;
Felipe Leme27f45732017-12-22 09:05:22 -080039import android.view.autofill.AutofillManager;
Felipe Leme452886a2017-11-27 13:09:13 -080040import android.view.autofill.Helper;
41
42import com.android.internal.util.Preconditions;
43
44import java.io.PrintWriter;
45import java.util.ArrayList;
46
47/**
Felipe Leme78172e72017-12-08 17:01:15 -080048 * Defines the user data used for
49 * <a href="AutofillService.html#FieldClassification">field classification</a>.
Felipe Leme452886a2017-11-27 13:09:13 -080050 */
Adam He1cb6f802018-12-10 15:15:49 -080051public final class UserData implements FieldClassificationUserData, Parcelable {
Felipe Leme452886a2017-11-27 13:09:13 -080052
53 private static final String TAG = "UserData";
54
Felipe Lemefebb7332018-02-12 18:12:55 -080055 private static final int DEFAULT_MAX_USER_DATA_SIZE = 50;
56 private static final int DEFAULT_MAX_CATEGORY_COUNT = 10;
Felipe Leme452886a2017-11-27 13:09:13 -080057 private static final int DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE = 10;
Felipe Lemefebb7332018-02-12 18:12:55 -080058 private static final int DEFAULT_MIN_VALUE_LENGTH = 3;
Felipe Leme452886a2017-11-27 13:09:13 -080059 private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
60
Felipe Lemef0baef742018-01-26 14:39:39 -080061 private final String mId;
Felipe Lemefebb7332018-02-12 18:12:55 -080062 private final String[] mCategoryIds;
Felipe Leme452886a2017-11-27 13:09:13 -080063 private final String[] mValues;
64
Adam He71d53e02018-11-06 14:03:26 -080065 private final String mDefaultAlgorithm;
66 private final Bundle mDefaultArgs;
67 private final ArrayMap<String, String> mCategoryAlgorithms;
68 private final ArrayMap<String, Bundle> mCategoryArgs;
69
Felipe Leme452886a2017-11-27 13:09:13 -080070 private UserData(Builder builder) {
Felipe Lemef0baef742018-01-26 14:39:39 -080071 mId = builder.mId;
Felipe Lemefebb7332018-02-12 18:12:55 -080072 mCategoryIds = new String[builder.mCategoryIds.size()];
73 builder.mCategoryIds.toArray(mCategoryIds);
Felipe Leme452886a2017-11-27 13:09:13 -080074 mValues = new String[builder.mValues.size()];
75 builder.mValues.toArray(mValues);
Adam He71d53e02018-11-06 14:03:26 -080076 builder.mValues.toArray(mValues);
77
78 mDefaultAlgorithm = builder.mDefaultAlgorithm;
79 mDefaultArgs = builder.mDefaultArgs;
80 mCategoryAlgorithms = builder.mCategoryAlgorithms;
81 mCategoryArgs = builder.mCategoryArgs;
Felipe Leme452886a2017-11-27 13:09:13 -080082 }
83
Felipe Leme27f45732017-12-22 09:05:22 -080084 /**
Adam He71d53e02018-11-06 14:03:26 -080085 * Gets the name of the default algorithm that is used to calculate
86 * {@link Match#getScore()} match scores}.
Felipe Leme27f45732017-12-22 09:05:22 -080087 */
88 @Nullable
Adam He1cb6f802018-12-10 15:15:49 -080089 @Override
Felipe Leme27f45732017-12-22 09:05:22 -080090 public String getFieldClassificationAlgorithm() {
Adam He71d53e02018-11-06 14:03:26 -080091 return mDefaultAlgorithm;
92 }
93
94 /** @hide */
Adam He1cb6f802018-12-10 15:15:49 -080095 @Override
Adam He71d53e02018-11-06 14:03:26 -080096 public Bundle getDefaultFieldClassificationArgs() {
97 return mDefaultArgs;
98 }
99
100 /**
101 * Gets the name of the algorithm corresponding to the specific autofill category
102 * that is used to calculate {@link Match#getScore() match scores}
103 *
104 * @param categoryId autofill field category
105 *
106 * @return String name of algorithm, null if none found.
107 */
108 @Nullable
Adam He1cb6f802018-12-10 15:15:49 -0800109 @Override
Adam He71d53e02018-11-06 14:03:26 -0800110 public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
111 Preconditions.checkNotNull(categoryId);
112 if (mCategoryAlgorithms == null || !mCategoryAlgorithms.containsKey(categoryId)) {
113 return null;
114 }
115 return mCategoryAlgorithms.get(categoryId);
Felipe Leme27f45732017-12-22 09:05:22 -0800116 }
117
Felipe Lemef0baef742018-01-26 14:39:39 -0800118 /**
119 * Gets the id.
120 */
121 public String getId() {
122 return mId;
123 }
124
Felipe Leme452886a2017-11-27 13:09:13 -0800125 /** @hide */
Adam He1cb6f802018-12-10 15:15:49 -0800126 @Override
Felipe Lemefebb7332018-02-12 18:12:55 -0800127 public String[] getCategoryIds() {
128 return mCategoryIds;
Felipe Leme452886a2017-11-27 13:09:13 -0800129 }
130
131 /** @hide */
Adam He1cb6f802018-12-10 15:15:49 -0800132 @Override
Felipe Leme452886a2017-11-27 13:09:13 -0800133 public String[] getValues() {
134 return mValues;
135 }
136
137 /** @hide */
Adam He71d53e02018-11-06 14:03:26 -0800138 @TestApi
Adam He1cb6f802018-12-10 15:15:49 -0800139 @Override
Adam He71d53e02018-11-06 14:03:26 -0800140 public ArrayMap<String, String> getFieldClassificationAlgorithms() {
141 return mCategoryAlgorithms;
142 }
143
144 /** @hide */
Adam He1cb6f802018-12-10 15:15:49 -0800145 @Override
Adam He71d53e02018-11-06 14:03:26 -0800146 public ArrayMap<String, Bundle> getFieldClassificationArgs() {
147 return mCategoryArgs;
148 }
149
150 /** @hide */
Felipe Leme452886a2017-11-27 13:09:13 -0800151 public void dump(String prefix, PrintWriter pw) {
Felipe Lemef0baef742018-01-26 14:39:39 -0800152 pw.print(prefix); pw.print("id: "); pw.print(mId);
Adam He71d53e02018-11-06 14:03:26 -0800153 pw.print(prefix); pw.print("Default Algorithm: "); pw.print(mDefaultAlgorithm);
154 pw.print(prefix); pw.print("Default Args"); pw.print(mDefaultArgs);
155 if (mCategoryAlgorithms != null && mCategoryAlgorithms.size() > 0) {
156 pw.print(prefix); pw.print("Algorithms per category: ");
157 for (int i = 0; i < mCategoryAlgorithms.size(); i++) {
158 pw.print(prefix); pw.print(prefix); pw.print(mCategoryAlgorithms.keyAt(i));
159 pw.print(": "); pw.println(Helper.getRedacted(mCategoryAlgorithms.valueAt(i)));
160 pw.print("args="); pw.print(mCategoryArgs.get(mCategoryAlgorithms.keyAt(i)));
161 }
162 }
Felipe Lemefebb7332018-02-12 18:12:55 -0800163 // Cannot disclose field ids or values because they could contain PII
164 pw.print(prefix); pw.print("Field ids size: "); pw.println(mCategoryIds.length);
165 for (int i = 0; i < mCategoryIds.length; i++) {
Felipe Leme5672def2017-12-04 14:57:09 -0800166 pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": ");
Felipe Lemefebb7332018-02-12 18:12:55 -0800167 pw.println(Helper.getRedacted(mCategoryIds[i]));
Felipe Leme5672def2017-12-04 14:57:09 -0800168 }
169 pw.print(prefix); pw.print("Values size: "); pw.println(mValues.length);
Felipe Leme452886a2017-11-27 13:09:13 -0800170 for (int i = 0; i < mValues.length; i++) {
Felipe Leme5672def2017-12-04 14:57:09 -0800171 pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": ");
172 pw.println(Helper.getRedacted(mValues[i]));
Felipe Leme452886a2017-11-27 13:09:13 -0800173 }
174 }
175
176 /** @hide */
177 public static void dumpConstraints(String prefix, PrintWriter pw) {
178 pw.print(prefix); pw.print("maxUserDataSize: "); pw.println(getMaxUserDataSize());
179 pw.print(prefix); pw.print("maxFieldClassificationIdsSize: ");
180 pw.println(getMaxFieldClassificationIdsSize());
Felipe Lemefebb7332018-02-12 18:12:55 -0800181 pw.print(prefix); pw.print("maxCategoryCount: "); pw.println(getMaxCategoryCount());
Felipe Leme452886a2017-11-27 13:09:13 -0800182 pw.print(prefix); pw.print("minValueLength: "); pw.println(getMinValueLength());
183 pw.print(prefix); pw.print("maxValueLength: "); pw.println(getMaxValueLength());
184 }
185
186 /**
187 * A builder for {@link UserData} objects.
Felipe Leme452886a2017-11-27 13:09:13 -0800188 */
Felipe Leme452886a2017-11-27 13:09:13 -0800189 public static final class Builder {
Felipe Lemef0baef742018-01-26 14:39:39 -0800190 private final String mId;
Felipe Lemefebb7332018-02-12 18:12:55 -0800191 private final ArrayList<String> mCategoryIds;
Felipe Leme452886a2017-11-27 13:09:13 -0800192 private final ArrayList<String> mValues;
Adam He71d53e02018-11-06 14:03:26 -0800193 private String mDefaultAlgorithm;
194 private Bundle mDefaultArgs;
195
196 // Map of autofill field categories to fleid classification algorithms and args
197 private ArrayMap<String, String> mCategoryAlgorithms;
198 private ArrayMap<String, Bundle> mCategoryArgs;
199
Felipe Leme452886a2017-11-27 13:09:13 -0800200 private boolean mDestroyed;
201
Felipe Lemefebb7332018-02-12 18:12:55 -0800202 // Non-persistent array used to limit the number of unique ids.
203 private final ArraySet<String> mUniqueCategoryIds;
Felipe Leme725bc912018-08-17 12:48:05 -0700204 // Non-persistent array used to ignore duplaicated value/category pairs.
205 private final ArraySet<String> mUniqueValueCategoryPairs;
206
Felipe Leme452886a2017-11-27 13:09:13 -0800207 /**
Felipe Leme78172e72017-12-08 17:01:15 -0800208 * Creates a new builder for the user data used for <a href="#FieldClassification">field
Felipe Leme452886a2017-11-27 13:09:13 -0800209 * classification</a>.
210 *
Felipe Lemefebb7332018-02-12 18:12:55 -0800211 * <p>The user data must contain at least one pair of {@code value} -> {@code categoryId},
212 * and more pairs can be added through the {@link #add(String, String)} method. For example:
213 *
214 * <pre class="prettyprint">
215 * new UserData.Builder("v1", "Bart Simpson", "name")
216 * .add("bart.simpson@example.com", "email")
217 * .add("el_barto@example.com", "email")
218 * .build();
219 * </pre>
Felipe Lemef0baef742018-01-26 14:39:39 -0800220 *
221 * @param id id used to identify the whole {@link UserData} object. This id is also returned
222 * by {@link AutofillManager#getUserDataId()}, which can be used to check if the
223 * {@link UserData} is up-to-date without fetching the whole object (through
224 * {@link AutofillManager#getUserData()}).
Felipe Lemefebb7332018-02-12 18:12:55 -0800225 *
Felipe Lemef0baef742018-01-26 14:39:39 -0800226 * @param value value of the user data.
Adam He71d53e02018-11-06 14:03:26 -0800227 * @param categoryId autofill field category.
Felipe Lemef0baef742018-01-26 14:39:39 -0800228 *
Felipe Leme329d0402017-12-06 09:22:43 -0800229 * @throws IllegalArgumentException if any of the following occurs:
Felipe Leme4cd1ae02018-03-15 15:55:05 -0700230 * <ul>
Felipe Lemefebb7332018-02-12 18:12:55 -0800231 * <li>{@code id} is empty</li>
232 * <li>{@code categoryId} is empty</li>
233 * <li>{@code value} is empty</li>
234 * <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()}</li>
235 * <li>the length of {@code value} is higher than
236 * {@link UserData#getMaxValueLength()}</li>
Felipe Leme4cd1ae02018-03-15 15:55:05 -0700237 * </ul>
Felipe Leme452886a2017-11-27 13:09:13 -0800238 */
Felipe Lemefebb7332018-02-12 18:12:55 -0800239 public Builder(@NonNull String id, @NonNull String value, @NonNull String categoryId) {
Felipe Lemef0baef742018-01-26 14:39:39 -0800240 mId = checkNotEmpty("id", id);
Felipe Lemefebb7332018-02-12 18:12:55 -0800241 checkNotEmpty("categoryId", categoryId);
Felipe Leme452886a2017-11-27 13:09:13 -0800242 checkValidValue(value);
Felipe Lemefebb7332018-02-12 18:12:55 -0800243 final int maxUserDataSize = getMaxUserDataSize();
244 mCategoryIds = new ArrayList<>(maxUserDataSize);
245 mValues = new ArrayList<>(maxUserDataSize);
Felipe Leme725bc912018-08-17 12:48:05 -0700246 mUniqueValueCategoryPairs = new ArraySet<>(maxUserDataSize);
Adam He71d53e02018-11-06 14:03:26 -0800247
Felipe Lemefebb7332018-02-12 18:12:55 -0800248 mUniqueCategoryIds = new ArraySet<>(getMaxCategoryCount());
249
250 addMapping(value, categoryId);
Felipe Leme452886a2017-11-27 13:09:13 -0800251 }
252
253 /**
Adam He71d53e02018-11-06 14:03:26 -0800254 * Sets the default algorithm used for
255 * <a href="#FieldClassification">field classification</a>.
Felipe Leme27f45732017-12-22 09:05:22 -0800256 *
257 * <p>The currently available algorithms can be retrieve through
258 * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
259 *
Felipe Lemed11a6622018-01-18 13:38:24 -0800260 * <p>If not set, the
261 * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is
262 * used instead.
Felipe Leme27f45732017-12-22 09:05:22 -0800263 *
264 * @param name name of the algorithm or {@code null} to used default.
265 * @param args optional arguments to the algorithm.
266 *
267 * @return this builder
268 */
269 public Builder setFieldClassificationAlgorithm(@Nullable String name,
270 @Nullable Bundle args) {
Felipe Lemefebb7332018-02-12 18:12:55 -0800271 throwIfDestroyed();
Adam He71d53e02018-11-06 14:03:26 -0800272 mDefaultAlgorithm = name;
273 mDefaultArgs = args;
274 return this;
275 }
276
277 /**
278 * Sets the algorithm used for <a href="#FieldClassification">field classification</a>
279 * for the specified category.
280 *
281 * <p>The currently available algorithms can be retrieved through
282 * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
283 *
284 * <p>If not set, the
285 * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is
286 * used instead.
287 *
288 * @param categoryId autofill field category.
289 * @param name name of the algorithm or {@code null} to used default.
290 * @param args optional arguments to the algorithm.
291 *
292 * @return this builder
293 */
294 public Builder setFieldClassificationAlgorithmForCategory(@NonNull String categoryId,
295 @Nullable String name, @Nullable Bundle args) {
296 throwIfDestroyed();
297 Preconditions.checkNotNull(categoryId);
298 if (mCategoryAlgorithms == null) {
299 mCategoryAlgorithms = new ArrayMap<>(getMaxCategoryCount());
300 }
301 if (mCategoryArgs == null) {
302 mCategoryArgs = new ArrayMap<>(getMaxCategoryCount());
303 }
304 mCategoryAlgorithms.put(categoryId, name);
305 mCategoryArgs.put(categoryId, args);
Felipe Leme27f45732017-12-22 09:05:22 -0800306 return this;
307 }
308
309 /**
Felipe Leme452886a2017-11-27 13:09:13 -0800310 * Adds a new value for user data.
311 *
Felipe Leme452886a2017-11-27 13:09:13 -0800312 * @param value value of the user data.
Felipe Lemefebb7332018-02-12 18:12:55 -0800313 * @param categoryId string used to identify the category the value is associated with.
Felipe Leme452886a2017-11-27 13:09:13 -0800314 *
Felipe Lemefebb7332018-02-12 18:12:55 -0800315 * @throws IllegalStateException if:
Felipe Leme4cd1ae02018-03-15 15:55:05 -0700316 * <ul>
Felipe Lemefebb7332018-02-12 18:12:55 -0800317 * <li>{@link #build()} already called</li>
Felipe Leme725bc912018-08-17 12:48:05 -0700318 * <li>the {@code value} has already been added (<b>Note: </b> this restriction was
319 * lifted on Android {@link android.os.Build.VERSION_CODES#Q} and later)</li>
Felipe Lemefebb7332018-02-12 18:12:55 -0800320 * <li>the number of unique {@code categoryId} values added so far is more than
321 * {@link UserData#getMaxCategoryCount()}</li>
322 * <li>the number of {@code values} added so far is is more than
323 * {@link UserData#getMaxUserDataSize()}</li>
Felipe Leme4cd1ae02018-03-15 15:55:05 -0700324 * </ul>
Felipe Leme452886a2017-11-27 13:09:13 -0800325 *
Felipe Lemefebb7332018-02-12 18:12:55 -0800326 * @throws IllegalArgumentException if any of the following occurs:
Felipe Leme4cd1ae02018-03-15 15:55:05 -0700327 * <ul>
Felipe Lemefebb7332018-02-12 18:12:55 -0800328 * <li>{@code id} is empty</li>
329 * <li>{@code categoryId} is empty</li>
330 * <li>{@code value} is empty</li>
331 * <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()}</li>
332 * <li>the length of {@code value} is higher than
333 * {@link UserData#getMaxValueLength()}</li>
Felipe Leme4cd1ae02018-03-15 15:55:05 -0700334 * </ul>
Felipe Leme452886a2017-11-27 13:09:13 -0800335 */
Felipe Lemefebb7332018-02-12 18:12:55 -0800336 public Builder add(@NonNull String value, @NonNull String categoryId) {
Felipe Leme452886a2017-11-27 13:09:13 -0800337 throwIfDestroyed();
Felipe Lemefebb7332018-02-12 18:12:55 -0800338 checkNotEmpty("categoryId", categoryId);
Felipe Leme452886a2017-11-27 13:09:13 -0800339 checkValidValue(value);
340
Felipe Lemefebb7332018-02-12 18:12:55 -0800341 if (!mUniqueCategoryIds.contains(categoryId)) {
342 // New category - check size
343 Preconditions.checkState(mUniqueCategoryIds.size() < getMaxCategoryCount(),
344 "already added " + mUniqueCategoryIds.size() + " unique category ids");
Felipe Lemefebb7332018-02-12 18:12:55 -0800345 }
346
Felipe Lemefebb7332018-02-12 18:12:55 -0800347 Preconditions.checkState(mValues.size() < getMaxUserDataSize(),
348 "already added " + mValues.size() + " elements");
349 addMapping(value, categoryId);
Felipe Leme5672def2017-12-04 14:57:09 -0800350
Felipe Leme452886a2017-11-27 13:09:13 -0800351 return this;
352 }
353
Felipe Lemefebb7332018-02-12 18:12:55 -0800354 private void addMapping(@NonNull String value, @NonNull String categoryId) {
Felipe Leme725bc912018-08-17 12:48:05 -0700355 final String pair = value + ":" + categoryId;
356 if (mUniqueValueCategoryPairs.contains(pair)) {
357 // Don't include value on message because it could contain PII
358 Log.w(TAG, "Ignoring entry with same value / category");
359 return;
360 }
Felipe Lemefebb7332018-02-12 18:12:55 -0800361 mCategoryIds.add(categoryId);
362 mValues.add(value);
363 mUniqueCategoryIds.add(categoryId);
Felipe Leme725bc912018-08-17 12:48:05 -0700364 mUniqueValueCategoryPairs.add(pair);
Felipe Lemefebb7332018-02-12 18:12:55 -0800365 }
366
Felipe Lemef0baef742018-01-26 14:39:39 -0800367 private String checkNotEmpty(@NonNull String name, @Nullable String value) {
368 Preconditions.checkNotNull(value);
369 Preconditions.checkArgument(!TextUtils.isEmpty(value), "%s cannot be empty", name);
370 return value;
Felipe Leme452886a2017-11-27 13:09:13 -0800371 }
372
373 private void checkValidValue(@Nullable String value) {
374 Preconditions.checkNotNull(value);
375 final int length = value.length();
376 Preconditions.checkArgumentInRange(length, getMinValueLength(),
377 getMaxValueLength(), "value length (" + length + ")");
378 }
379
380 /**
381 * Creates a new {@link UserData} instance.
382 *
383 * <p>You should not interact with this builder once this method is called.
384 *
385 * @throws IllegalStateException if {@link #build()} was already called.
386 *
387 * @return The built dataset.
388 */
389 public UserData build() {
390 throwIfDestroyed();
391 mDestroyed = true;
392 return new UserData(this);
393 }
394
395 private void throwIfDestroyed() {
396 if (mDestroyed) {
397 throw new IllegalStateException("Already called #build()");
398 }
399 }
400 }
401
402 /////////////////////////////////////
403 // Object "contract" methods. //
404 /////////////////////////////////////
405 @Override
406 public String toString() {
407 if (!sDebug) return super.toString();
408
Adam He71d53e02018-11-06 14:03:26 -0800409 final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId);
Felipe Lemefebb7332018-02-12 18:12:55 -0800410 // Cannot disclose category ids or values because they could contain PII
411 builder.append(", categoryIds=");
412 Helper.appendRedacted(builder, mCategoryIds);
Felipe Leme452886a2017-11-27 13:09:13 -0800413 builder.append(", values=");
414 Helper.appendRedacted(builder, mValues);
415 return builder.append("]").toString();
416 }
417
418 /////////////////////////////////////
419 // Parcelable "contract" methods. //
420 /////////////////////////////////////
421
422 @Override
423 public int describeContents() {
424 return 0;
425 }
426
427 @Override
428 public void writeToParcel(Parcel parcel, int flags) {
Felipe Lemef0baef742018-01-26 14:39:39 -0800429 parcel.writeString(mId);
Felipe Lemefebb7332018-02-12 18:12:55 -0800430 parcel.writeStringArray(mCategoryIds);
Felipe Leme452886a2017-11-27 13:09:13 -0800431 parcel.writeStringArray(mValues);
Adam He71d53e02018-11-06 14:03:26 -0800432 parcel.writeString(mDefaultAlgorithm);
433 parcel.writeBundle(mDefaultArgs);
434 parcel.writeMap(mCategoryAlgorithms);
435 parcel.writeMap(mCategoryArgs);
Felipe Leme452886a2017-11-27 13:09:13 -0800436 }
437
438 public static final Parcelable.Creator<UserData> CREATOR =
439 new Parcelable.Creator<UserData>() {
440 @Override
441 public UserData createFromParcel(Parcel parcel) {
442 // Always go through the builder to ensure the data ingested by
443 // the system obeys the contract of the builder to avoid attacks
444 // using specially crafted parcels.
Felipe Lemef0baef742018-01-26 14:39:39 -0800445 final String id = parcel.readString();
Felipe Lemefebb7332018-02-12 18:12:55 -0800446 final String[] categoryIds = parcel.readStringArray();
Felipe Leme452886a2017-11-27 13:09:13 -0800447 final String[] values = parcel.readStringArray();
Adam He71d53e02018-11-06 14:03:26 -0800448 final String defaultAlgorithm = parcel.readString();
449 final Bundle defaultArgs = parcel.readBundle();
450 final ArrayMap<String, String> categoryAlgorithms = new ArrayMap<>();
451 parcel.readMap(categoryAlgorithms, String.class.getClassLoader());
452 final ArrayMap<String, Bundle> categoryArgs = new ArrayMap<>();
453 parcel.readMap(categoryArgs, Bundle.class.getClassLoader());
454
Felipe Lemefebb7332018-02-12 18:12:55 -0800455 final Builder builder = new Builder(id, values[0], categoryIds[0])
Adam He71d53e02018-11-06 14:03:26 -0800456 .setFieldClassificationAlgorithm(defaultAlgorithm, defaultArgs);
457
Felipe Lemefebb7332018-02-12 18:12:55 -0800458 for (int i = 1; i < categoryIds.length; i++) {
Adam He71d53e02018-11-06 14:03:26 -0800459 String categoryId = categoryIds[i];
460 builder.add(values[i], categoryId);
461 }
462
463 final int size = categoryAlgorithms.size();
464 if (size > 0) {
465 for (int i = 0; i < size; i++) {
466 final String categoryId = categoryAlgorithms.keyAt(i);
467 builder.setFieldClassificationAlgorithmForCategory(categoryId,
468 categoryAlgorithms.valueAt(i), categoryArgs.get(categoryId));
469 }
Felipe Leme452886a2017-11-27 13:09:13 -0800470 }
471 return builder.build();
472 }
473
474 @Override
475 public UserData[] newArray(int size) {
476 return new UserData[size];
477 }
478 };
479
480 /**
481 * Gets the maximum number of values that can be added to a {@link UserData}.
482 */
483 public static int getMaxUserDataSize() {
484 return getInt(AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, DEFAULT_MAX_USER_DATA_SIZE);
485 }
486
487 /**
488 * Gets the maximum number of ids that can be passed to {@link
489 * FillResponse.Builder#setFieldClassificationIds(android.view.autofill.AutofillId...)}.
490 */
491 public static int getMaxFieldClassificationIdsSize() {
492 return getInt(AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
493 DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE);
494 }
495
496 /**
Felipe Lemefebb7332018-02-12 18:12:55 -0800497 * Gets the maximum number of unique category ids that can be passed to
498 * the builder's constructor and {@link Builder#add(String, String)}.
499 */
500 public static int getMaxCategoryCount() {
501 return getInt(AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, DEFAULT_MAX_CATEGORY_COUNT);
502 }
503
504 /**
Felipe Leme78172e72017-12-08 17:01:15 -0800505 * Gets the minimum length of values passed to the builder's constructor or
506 * or {@link Builder#add(String, String)}.
Felipe Leme452886a2017-11-27 13:09:13 -0800507 */
508 public static int getMinValueLength() {
509 return getInt(AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, DEFAULT_MIN_VALUE_LENGTH);
510 }
511
512 /**
Felipe Leme78172e72017-12-08 17:01:15 -0800513 * Gets the maximum length of values passed to the builder's constructor or
514 * or {@link Builder#add(String, String)}.
Felipe Leme452886a2017-11-27 13:09:13 -0800515 */
516 public static int getMaxValueLength() {
517 return getInt(AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, DEFAULT_MAX_VALUE_LENGTH);
518 }
519
520 private static int getInt(String settings, int defaultValue) {
521 ContentResolver cr = null;
522 final ActivityThread at = ActivityThread.currentActivityThread();
523 if (at != null) {
524 cr = at.getApplication().getContentResolver();
525 }
526
527 if (cr == null) {
528 Log.w(TAG, "Could not read from " + settings + "; hardcoding " + defaultValue);
529 return defaultValue;
530 }
531 return Settings.Secure.getInt(cr, settings, defaultValue);
532 }
533}