UserData.java revision 329d04097e1db9b6f801972d94f56c5b56c09e8a
1/* 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 18import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE; 19import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE; 20import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH; 21import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH; 22import static android.view.autofill.Helper.sDebug; 23 24import android.annotation.NonNull; 25import android.annotation.Nullable; 26import android.annotation.TestApi; 27import android.app.ActivityThread; 28import android.content.ContentResolver; 29import android.os.Parcel; 30import android.os.Parcelable; 31import android.provider.Settings; 32import android.util.Log; 33import android.view.autofill.Helper; 34 35import com.android.internal.util.Preconditions; 36 37import java.io.PrintWriter; 38import java.util.ArrayList; 39 40/** 41 * Class used by service to improve autofillable fields detection by tracking the meaning of fields 42 * manually edited by the user (when they match values provided by the service). 43 * 44 * TODO(b/67867469): 45 * - improve javadoc / add link to section on AutofillService 46 * - unhide / remove testApi 47 * @hide 48 */ 49@TestApi 50public final class UserData implements Parcelable { 51 52 private static final String TAG = "UserData"; 53 54 private static final int DEFAULT_MAX_USER_DATA_SIZE = 10; 55 private static final int DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE = 10; 56 private static final int DEFAULT_MIN_VALUE_LENGTH = 5; 57 private static final int DEFAULT_MAX_VALUE_LENGTH = 100; 58 59 private final InternalScorer mScorer; 60 private final String[] mRemoteIds; 61 private final String[] mValues; 62 63 private UserData(Builder builder) { 64 mScorer = builder.mScorer; 65 mRemoteIds = new String[builder.mRemoteIds.size()]; 66 builder.mRemoteIds.toArray(mRemoteIds); 67 mValues = new String[builder.mValues.size()]; 68 builder.mValues.toArray(mValues); 69 } 70 71 /** @hide */ 72 public InternalScorer getScorer() { 73 return mScorer; 74 } 75 76 /** @hide */ 77 public String[] getRemoteIds() { 78 return mRemoteIds; 79 } 80 81 /** @hide */ 82 public String[] getValues() { 83 return mValues; 84 } 85 86 /** @hide */ 87 public void dump(String prefix, PrintWriter pw) { 88 pw.print(prefix); pw.print("Scorer: "); pw.println(mScorer); 89 // Cannot disclose remote ids or values because they could contain PII 90 pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length); 91 for (int i = 0; i < mRemoteIds.length; i++) { 92 pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); 93 pw.println(Helper.getRedacted(mRemoteIds[i])); 94 } 95 pw.print(prefix); pw.print("Values size: "); pw.println(mValues.length); 96 for (int i = 0; i < mValues.length; i++) { 97 pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); 98 pw.println(Helper.getRedacted(mValues[i])); 99 } 100 } 101 102 /** @hide */ 103 public static void dumpConstraints(String prefix, PrintWriter pw) { 104 pw.print(prefix); pw.print("maxUserDataSize: "); pw.println(getMaxUserDataSize()); 105 pw.print(prefix); pw.print("maxFieldClassificationIdsSize: "); 106 pw.println(getMaxFieldClassificationIdsSize()); 107 pw.print(prefix); pw.print("minValueLength: "); pw.println(getMinValueLength()); 108 pw.print(prefix); pw.print("maxValueLength: "); pw.println(getMaxValueLength()); 109 } 110 111 /** 112 * A builder for {@link UserData} objects. 113 * 114 * TODO(b/67867469): unhide / remove testApi 115 * 116 * @hide 117 */ 118 @TestApi 119 public static final class Builder { 120 private final InternalScorer mScorer; 121 private final ArrayList<String> mRemoteIds; 122 private final ArrayList<String> mValues; 123 private boolean mDestroyed; 124 125 /** 126 * Creates a new builder for the user data used for <a href="#FieldsClassification">fields 127 * classification</a>. 128 * 129 * @throws IllegalArgumentException if any of the following occurs: 130 * <ol> 131 * <li>{@code remoteId} is empty 132 * <li>{@code value} is empty 133 * <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()} 134 * <li>the length of {@code value} is higher than {@link UserData#getMaxValueLength()} 135 * <li>{@code scorer} is not instance of a class provided by the Android System. 136 * </ol> 137 */ 138 public Builder(@NonNull Scorer scorer, @NonNull String remoteId, @NonNull String value) { 139 Preconditions.checkArgument((scorer instanceof InternalScorer), 140 "not provided by Android System: " + scorer); 141 mScorer = (InternalScorer) scorer; 142 checkValidRemoteId(remoteId); 143 checkValidValue(value); 144 final int capacity = getMaxUserDataSize(); 145 mRemoteIds = new ArrayList<>(capacity); 146 mValues = new ArrayList<>(capacity); 147 mRemoteIds.add(remoteId); 148 mValues.add(value); 149 } 150 151 /** 152 * Adds a new value for user data. 153 * 154 * @param remoteId unique string used to identify the user data. 155 * @param value value of the user data. 156 * 157 * @throws IllegalStateException if {@link #build()} or 158 * {@link #add(String, String)} with the same {@code remoteId} has already 159 * been called, or if the number of values add (i.e., calls made to this method plus 160 * constructor) is more than {@link UserData#getMaxUserDataSize()}. 161 * 162 * @throws IllegalArgumentException if {@code remoteId} or {@code value} are empty or if the 163 * length of {@code value} is lower than {@link UserData#getMinValueLength()} 164 * or higher than {@link UserData#getMaxValueLength()}. 165 */ 166 public Builder add(@NonNull String remoteId, @NonNull String value) { 167 throwIfDestroyed(); 168 checkValidRemoteId(remoteId); 169 checkValidValue(value); 170 171 Preconditions.checkState(!mRemoteIds.contains(remoteId), 172 // Don't include remoteId on message because it could contain PII 173 "already has entry with same remoteId"); 174 Preconditions.checkState(!mValues.contains(value), 175 // Don't include remoteId on message because it could contain PII 176 "already has entry with same value"); 177 Preconditions.checkState(mRemoteIds.size() < getMaxUserDataSize(), 178 "already added " + mRemoteIds.size() + " elements"); 179 mRemoteIds.add(remoteId); 180 mValues.add(value); 181 182 return this; 183 } 184 185 private void checkValidRemoteId(@Nullable String remoteId) { 186 Preconditions.checkNotNull(remoteId); 187 Preconditions.checkArgument(!remoteId.isEmpty(), "remoteId cannot be empty"); 188 } 189 190 private void checkValidValue(@Nullable String value) { 191 Preconditions.checkNotNull(value); 192 final int length = value.length(); 193 Preconditions.checkArgumentInRange(length, getMinValueLength(), 194 getMaxValueLength(), "value length (" + length + ")"); 195 } 196 197 /** 198 * Creates a new {@link UserData} instance. 199 * 200 * <p>You should not interact with this builder once this method is called. 201 * 202 * @throws IllegalStateException if {@link #build()} was already called. 203 * 204 * @return The built dataset. 205 */ 206 public UserData build() { 207 throwIfDestroyed(); 208 mDestroyed = true; 209 return new UserData(this); 210 } 211 212 private void throwIfDestroyed() { 213 if (mDestroyed) { 214 throw new IllegalStateException("Already called #build()"); 215 } 216 } 217 } 218 219 ///////////////////////////////////// 220 // Object "contract" methods. // 221 ///////////////////////////////////// 222 @Override 223 public String toString() { 224 if (!sDebug) return super.toString(); 225 226 final StringBuilder builder = new StringBuilder("UserData: [scorer=").append(mScorer); 227 // Cannot disclose remote ids or values because they could contain PII 228 builder.append(", remoteIds="); 229 Helper.appendRedacted(builder, mRemoteIds); 230 builder.append(", values="); 231 Helper.appendRedacted(builder, mValues); 232 return builder.append("]").toString(); 233 } 234 235 ///////////////////////////////////// 236 // Parcelable "contract" methods. // 237 ///////////////////////////////////// 238 239 @Override 240 public int describeContents() { 241 return 0; 242 } 243 244 @Override 245 public void writeToParcel(Parcel parcel, int flags) { 246 parcel.writeParcelable(mScorer, flags); 247 parcel.writeStringArray(mRemoteIds); 248 parcel.writeStringArray(mValues); 249 } 250 251 public static final Parcelable.Creator<UserData> CREATOR = 252 new Parcelable.Creator<UserData>() { 253 @Override 254 public UserData createFromParcel(Parcel parcel) { 255 // Always go through the builder to ensure the data ingested by 256 // the system obeys the contract of the builder to avoid attacks 257 // using specially crafted parcels. 258 final InternalScorer scorer = parcel.readParcelable(null); 259 final String[] remoteIds = parcel.readStringArray(); 260 final String[] values = parcel.readStringArray(); 261 final Builder builder = new Builder(scorer, remoteIds[0], values[0]); 262 for (int i = 1; i < remoteIds.length; i++) { 263 builder.add(remoteIds[i], values[i]); 264 } 265 return builder.build(); 266 } 267 268 @Override 269 public UserData[] newArray(int size) { 270 return new UserData[size]; 271 } 272 }; 273 274 /** 275 * Gets the maximum number of values that can be added to a {@link UserData}. 276 */ 277 public static int getMaxUserDataSize() { 278 return getInt(AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, DEFAULT_MAX_USER_DATA_SIZE); 279 } 280 281 /** 282 * Gets the maximum number of ids that can be passed to {@link 283 * FillResponse.Builder#setFieldClassificationIds(android.view.autofill.AutofillId...)}. 284 */ 285 public static int getMaxFieldClassificationIdsSize() { 286 return getInt(AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, 287 DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE); 288 } 289 290 /** 291 * Gets the minimum length of values passed to {@link Builder#Builder(Scorer, String, String)}. 292 */ 293 public static int getMinValueLength() { 294 return getInt(AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, DEFAULT_MIN_VALUE_LENGTH); 295 } 296 297 /** 298 * Gets the maximum length of values passed to {@link Builder#Builder(Scorer, String, String)}. 299 */ 300 public static int getMaxValueLength() { 301 return getInt(AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, DEFAULT_MAX_VALUE_LENGTH); 302 } 303 304 private static int getInt(String settings, int defaultValue) { 305 ContentResolver cr = null; 306 final ActivityThread at = ActivityThread.currentActivityThread(); 307 if (at != null) { 308 cr = at.getApplication().getContentResolver(); 309 } 310 311 if (cr == null) { 312 Log.w(TAG, "Could not read from " + settings + "; hardcoding " + defaultValue); 313 return defaultValue; 314 } 315 return Settings.Secure.getInt(cr, settings, defaultValue); 316 } 317} 318