diff options
Diffstat (limited to 'android/service/autofill/UserData.java')
-rw-r--r-- | android/service/autofill/UserData.java | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/android/service/autofill/UserData.java b/android/service/autofill/UserData.java new file mode 100644 index 00000000..f0cc360f --- /dev/null +++ b/android/service/autofill/UserData.java @@ -0,0 +1,307 @@ +/* + * Copyright 2017 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 android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE; +import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE; +import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH; +import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH; +import static android.view.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.content.ContentResolver; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.Settings; +import android.util.Log; +import android.view.autofill.Helper; + +import com.android.internal.util.Preconditions; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Defines the user data used for + * <a href="AutofillService.html#FieldClassification">field classification</a>. + */ +public final class UserData implements Parcelable { + + private static final String TAG = "UserData"; + + private static final int DEFAULT_MAX_USER_DATA_SIZE = 10; + private static final int DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE = 10; + private static final int DEFAULT_MIN_VALUE_LENGTH = 5; + private static final int DEFAULT_MAX_VALUE_LENGTH = 100; + + private final InternalScorer mScorer; + private final String[] mRemoteIds; + private final String[] mValues; + + private UserData(Builder builder) { + mScorer = builder.mScorer; + mRemoteIds = new String[builder.mRemoteIds.size()]; + builder.mRemoteIds.toArray(mRemoteIds); + mValues = new String[builder.mValues.size()]; + builder.mValues.toArray(mValues); + } + + /** @hide */ + public InternalScorer getScorer() { + return mScorer; + } + + /** @hide */ + public String[] getRemoteIds() { + return mRemoteIds; + } + + /** @hide */ + public String[] getValues() { + return mValues; + } + + /** @hide */ + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("Scorer: "); pw.println(mScorer); + // Cannot disclose remote ids or values because they could contain PII + pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length); + for (int i = 0; i < mRemoteIds.length; i++) { + pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); + pw.println(Helper.getRedacted(mRemoteIds[i])); + } + pw.print(prefix); pw.print("Values size: "); pw.println(mValues.length); + for (int i = 0; i < mValues.length; i++) { + pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); + pw.println(Helper.getRedacted(mValues[i])); + } + } + + /** @hide */ + public static void dumpConstraints(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("maxUserDataSize: "); pw.println(getMaxUserDataSize()); + pw.print(prefix); pw.print("maxFieldClassificationIdsSize: "); + pw.println(getMaxFieldClassificationIdsSize()); + pw.print(prefix); pw.print("minValueLength: "); pw.println(getMinValueLength()); + pw.print(prefix); pw.print("maxValueLength: "); pw.println(getMaxValueLength()); + } + + /** + * A builder for {@link UserData} objects. + */ + public static final class Builder { + private final InternalScorer mScorer; + private final ArrayList<String> mRemoteIds; + private final ArrayList<String> mValues; + private boolean mDestroyed; + + /** + * Creates a new builder for the user data used for <a href="#FieldClassification">field + * classification</a>. + * + * @throws IllegalArgumentException if any of the following occurs: + * <ol> + * <li>{@code remoteId} is empty + * <li>{@code value} is empty + * <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()} + * <li>the length of {@code value} is higher than {@link UserData#getMaxValueLength()} + * <li>{@code scorer} is not instance of a class provided by the Android System. + * </ol> + */ + public Builder(@NonNull Scorer scorer, @NonNull String remoteId, @NonNull String value) { + Preconditions.checkArgument((scorer instanceof InternalScorer), + "not provided by Android System: " + scorer); + mScorer = (InternalScorer) scorer; + checkValidRemoteId(remoteId); + checkValidValue(value); + final int capacity = getMaxUserDataSize(); + mRemoteIds = new ArrayList<>(capacity); + mValues = new ArrayList<>(capacity); + mRemoteIds.add(remoteId); + mValues.add(value); + } + + /** + * Adds a new value for user data. + * + * @param remoteId unique string used to identify the user data. + * @param value value of the user data. + * + * @throws IllegalStateException if {@link #build()} or + * {@link #add(String, String)} with the same {@code remoteId} has already + * been called, or if the number of values add (i.e., calls made to this method plus + * constructor) is more than {@link UserData#getMaxUserDataSize()}. + * + * @throws IllegalArgumentException if {@code remoteId} or {@code value} are empty or if the + * length of {@code value} is lower than {@link UserData#getMinValueLength()} + * or higher than {@link UserData#getMaxValueLength()}. + */ + public Builder add(@NonNull String remoteId, @NonNull String value) { + throwIfDestroyed(); + checkValidRemoteId(remoteId); + checkValidValue(value); + + Preconditions.checkState(!mRemoteIds.contains(remoteId), + // Don't include remoteId on message because it could contain PII + "already has entry with same remoteId"); + Preconditions.checkState(!mValues.contains(value), + // Don't include remoteId on message because it could contain PII + "already has entry with same value"); + Preconditions.checkState(mRemoteIds.size() < getMaxUserDataSize(), + "already added " + mRemoteIds.size() + " elements"); + mRemoteIds.add(remoteId); + mValues.add(value); + + return this; + } + + private void checkValidRemoteId(@Nullable String remoteId) { + Preconditions.checkNotNull(remoteId); + Preconditions.checkArgument(!remoteId.isEmpty(), "remoteId cannot be empty"); + } + + private void checkValidValue(@Nullable String value) { + Preconditions.checkNotNull(value); + final int length = value.length(); + Preconditions.checkArgumentInRange(length, getMinValueLength(), + getMaxValueLength(), "value length (" + length + ")"); + } + + /** + * Creates a new {@link UserData} instance. + * + * <p>You should not interact with this builder once this method is called. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return The built dataset. + */ + public UserData build() { + throwIfDestroyed(); + mDestroyed = true; + return new UserData(this); + } + + private void throwIfDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("Already called #build()"); + } + } + } + + ///////////////////////////////////// + // Object "contract" methods. // + ///////////////////////////////////// + @Override + public String toString() { + if (!sDebug) return super.toString(); + + final StringBuilder builder = new StringBuilder("UserData: [scorer=").append(mScorer); + // Cannot disclose remote ids or values because they could contain PII + builder.append(", remoteIds="); + Helper.appendRedacted(builder, mRemoteIds); + builder.append(", values="); + Helper.appendRedacted(builder, mValues); + return builder.append("]").toString(); + } + + ///////////////////////////////////// + // Parcelable "contract" methods. // + ///////////////////////////////////// + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mScorer, flags); + parcel.writeStringArray(mRemoteIds); + parcel.writeStringArray(mValues); + } + + public static final Parcelable.Creator<UserData> CREATOR = + new Parcelable.Creator<UserData>() { + @Override + public UserData createFromParcel(Parcel parcel) { + // Always go through the builder to ensure the data ingested by + // the system obeys the contract of the builder to avoid attacks + // using specially crafted parcels. + final InternalScorer scorer = parcel.readParcelable(null); + final String[] remoteIds = parcel.readStringArray(); + final String[] values = parcel.readStringArray(); + final Builder builder = new Builder(scorer, remoteIds[0], values[0]); + for (int i = 1; i < remoteIds.length; i++) { + builder.add(remoteIds[i], values[i]); + } + return builder.build(); + } + + @Override + public UserData[] newArray(int size) { + return new UserData[size]; + } + }; + + /** + * Gets the maximum number of values that can be added to a {@link UserData}. + */ + public static int getMaxUserDataSize() { + return getInt(AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, DEFAULT_MAX_USER_DATA_SIZE); + } + + /** + * Gets the maximum number of ids that can be passed to {@link + * FillResponse.Builder#setFieldClassificationIds(android.view.autofill.AutofillId...)}. + */ + public static int getMaxFieldClassificationIdsSize() { + return getInt(AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, + DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE); + } + + /** + * Gets the minimum length of values passed to the builder's constructor or + * or {@link Builder#add(String, String)}. + */ + public static int getMinValueLength() { + return getInt(AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, DEFAULT_MIN_VALUE_LENGTH); + } + + /** + * Gets the maximum length of values passed to the builder's constructor or + * or {@link Builder#add(String, String)}. + */ + public static int getMaxValueLength() { + return getInt(AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, DEFAULT_MAX_VALUE_LENGTH); + } + + private static int getInt(String settings, int defaultValue) { + ContentResolver cr = null; + final ActivityThread at = ActivityThread.currentActivityThread(); + if (at != null) { + cr = at.getApplication().getContentResolver(); + } + + if (cr == null) { + Log.w(TAG, "Could not read from " + settings + "; hardcoding " + defaultValue); + return defaultValue; + } + return Settings.Secure.getInt(cr, settings, defaultValue); + } +} |