summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2017-12-04 19:46:51 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2017-12-04 19:46:51 +0000
commit3ce82e34e6684951ba89e2bfb49e8ef3ca8c951d (patch)
tree49fdea236eb499397833079edaf37de0d4e3e152 /core/java
parenta18d357251494f822b7a2edff443f3a7da947786 (diff)
parent452886a5b8d7cb94ba0c53e8976ff558980db1e5 (diff)
Merge "Refactored field detection mechanism to support multiple fields."
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/provider/Settings.java36
-rw-r--r--core/java/android/service/autofill/AutofillService.java8
-rw-r--r--core/java/android/service/autofill/FieldsDetection.java127
-rw-r--r--core/java/android/service/autofill/FillEventHistory.java35
-rw-r--r--core/java/android/service/autofill/FillResponse.java58
-rw-r--r--core/java/android/service/autofill/UserData.aidl20
-rw-r--r--core/java/android/service/autofill/UserData.java288
-rw-r--r--core/java/android/view/autofill/AutofillManager.java50
-rw-r--r--core/java/android/view/autofill/AutofillValue.java2
-rw-r--r--core/java/android/view/autofill/Helper.java31
-rw-r--r--core/java/android/view/autofill/IAutoFillManager.aidl3
11 files changed, 482 insertions, 176 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c4c679885c09..53832c7ddf33 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5333,6 +5333,42 @@ public final class Settings {
public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection";
/**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE =
+ "autofill_user_data_max_user_data_size";
+
+ /**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE =
+ "autofill_user_data_max_field_classification_size";
+
+ /**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH =
+ "autofill_user_data_max_value_length";
+
+ /**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
+ "autofill_user_data_min_value_length";
+
+ /**
* @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/
@Deprecated
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index cd362c712b3f..1afa8b3eb4dd 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -440,7 +440,6 @@ import com.android.internal.os.SomeArgs;
* save(username, password);
* </pre>
*
- *
* <a name="Privacy"></a>
* <h3>Privacy</h3>
*
@@ -453,6 +452,13 @@ import com.android.internal.os.SomeArgs;
* <p>Because this data could contain PII (Personally Identifiable Information, such as username or
* email address), the service should only use it locally (i.e., in the app's process) for
* heuristics purposes, but it should not be sent to external servers.
+ *
+ * <a name="FieldsClassification"></a>
+ * <h3>Metrics and fields classification</h3
+ *
+ * <p>TODO(b/67867469): document it or remove this section; in particular, document the relationship
+ * between set/getUserData(), FillResponse.setFieldClassificationIds(), and
+ * FillEventHistory.getFieldsClassification.
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/core/java/android/service/autofill/FieldsDetection.java b/core/java/android/service/autofill/FieldsDetection.java
deleted file mode 100644
index 550ecf687349..000000000000
--- a/core/java/android/service/autofill/FieldsDetection.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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 android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.autofill.AutofillId;
-
-/**
- * Class by service to improve autofillable fields detection by tracking the meaning of fields
- * manually edited by the user (when they match values provided by the service).
- *
- * TODO(b/67867469):
- * - proper javadoc
- * - unhide / remove testApi
- * - add FieldsDetection management so service can set it just once and reference it in further
- * calls to improve performance (and also API to refresh it)
- * - rename to FieldsDetectionInfo or FieldClassification? (same for CTS tests)
- * - add FieldsDetectionUnitTest once API is well-defined
- * @hide
- */
-@TestApi
-public final class FieldsDetection implements Parcelable {
-
- private final AutofillId mFieldId;
- private final String mRemoteId;
- private final String mValue;
-
- /**
- * Creates a field detection for just one field / value pair.
- *
- * @param fieldId autofill id of the field in the screen.
- * @param remoteId id used by the service to identify the field later.
- * @param value field value known to the service.
- *
- * TODO(b/67867469):
- * - proper javadoc
- * - change signature to allow more fields / values / match methods
- * - might also need to use a builder, where the constructor is the id for the fieldsdetector
- * - might need id for values as well
- * - add @NonNull / check it / add unit tests
- * - make 'value' input more generic so it can accept distance-based match and other matches
- * - throw exception if field value is less than X characters (somewhere between 7-10)
- * - make sure to limit total number of fields to around 10 or so
- * - use AutofillValue instead of String (so it can compare dates, for example)
- */
- public FieldsDetection(AutofillId fieldId, String remoteId, String value) {
- mFieldId = fieldId;
- mRemoteId = remoteId;
- mValue = value;
- }
-
- /** @hide */
- public AutofillId getFieldId() {
- return mFieldId;
- }
-
- /** @hide */
- public String getRemoteId() {
- return mRemoteId;
- }
-
- /** @hide */
- public String getValue() {
- return mValue;
- }
-
- /////////////////////////////////////
- // Object "contract" methods. //
- /////////////////////////////////////
- @Override
- public String toString() {
- // Cannot disclose remoteId or value because they could contain PII
- return new StringBuilder("FieldsDetection: [field=").append(mFieldId)
- .append(", remoteId_length=").append(mRemoteId.length())
- .append(", value_length=").append(mValue.length())
- .append("]").toString();
- }
-
- /////////////////////////////////////
- // Parcelable "contract" methods. //
- /////////////////////////////////////
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeParcelable(mFieldId, flags);
- parcel.writeString(mRemoteId);
- parcel.writeString(mValue);
- }
-
- public static final Parcelable.Creator<FieldsDetection> CREATOR =
- new Parcelable.Creator<FieldsDetection>() {
- @Override
- public FieldsDetection createFromParcel(Parcel parcel) {
- // TODO(b/67867469): remove comment below if it does not use a builder at the end
- // 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.
- return new FieldsDetection(parcel.readParcelable(null), parcel.readString(),
- parcel.readString());
- }
-
- @Override
- public FieldsDetection[] newArray(int size) {
- return new FieldsDetection[size];
- }
- };
-}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 736d9ef48d04..eedb972e4ea6 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -58,11 +58,6 @@ import java.util.Set;
*/
public final class FillEventHistory implements Parcelable {
/**
- * Not in parcel. The UID of the {@link AutofillService} that created the {@link FillResponse}.
- */
- private final int mServiceUid;
-
- /**
* Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
*/
private final int mSessionId;
@@ -70,17 +65,6 @@ public final class FillEventHistory implements Parcelable {
@Nullable private final Bundle mClientState;
@Nullable List<Event> mEvents;
- /**
- * Gets the UID of the {@link AutofillService} that created the {@link FillResponse}.
- *
- * @return The UID of the {@link AutofillService}
- *
- * @hide
- */
- public int getServiceUid() {
- return mServiceUid;
- }
-
/** @hide */
public int getSessionId() {
return mSessionId;
@@ -123,9 +107,8 @@ public final class FillEventHistory implements Parcelable {
/**
* @hide
*/
- public FillEventHistory(int serviceUid, int sessionId, @Nullable Bundle clientState) {
+ public FillEventHistory(int sessionId, @Nullable Bundle clientState) {
mClientState = clientState;
- mServiceUid = serviceUid;
mSessionId = sessionId;
}
@@ -364,16 +347,17 @@ public final class FillEventHistory implements Parcelable {
}
/**
- * Gets the results of the last {@link FieldsDetection} request.
+ * Gets the results of the last fields classification request.
*
* @return map of edit-distance match ({@code 0} means full match,
- * {@code 1} means 1 character different, etc...) by remote id (as set in the
- * {@link FieldsDetection} constructor), or {@code null} if none of the user-input values
+ * {@code 1} means 1 character different, etc...) by remote id (as set on
+ * {@link UserData.Builder#add(String, android.view.autofill.AutofillValue)}),
+ * or {@code null} if none of the user-input values
* matched the requested detection.
*
* <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
- * service requested {@link FillResponse.Builder#setFieldsDetection(FieldsDetection) fields
- * detection}.
+ * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)
+ * fields detection}.
*
* TODO(b/67867469):
* - improve javadoc
@@ -382,11 +366,12 @@ public final class FillEventHistory implements Parcelable {
* - unhide
* - unhide / remove testApi
* - add @NonNull / check it / add unit tests
+ * - add link to AutofillService #FieldsClassification anchor
*
* @hide
*/
@TestApi
- @NonNull public Map<String, Integer> getDetectedFields() {
+ @NonNull public Map<String, Integer> getFieldsClassification() {
if (mDetectedRemoteId == null || mDetectedFieldScore == -1) {
return Collections.emptyMap();
}
@@ -534,7 +519,7 @@ public final class FillEventHistory implements Parcelable {
new Parcelable.Creator<FillEventHistory>() {
@Override
public FillEventHistory createFromParcel(Parcel parcel) {
- FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
+ FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
final int numEvents = parcel.readInt();
for (int i = 0; i < numEvents; i++) {
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 84a0974d11cd..dff40ffc3e18 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -76,7 +76,7 @@ public final class FillResponse implements Parcelable {
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
private final long mDisableDuration;
- private final @Nullable FieldsDetection mFieldsDetection;
+ private final @Nullable AutofillId[] mFieldClassificationIds;
private final int mFlags;
private int mRequestId;
@@ -89,7 +89,7 @@ public final class FillResponse implements Parcelable {
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
mDisableDuration = builder.mDisableDuration;
- mFieldsDetection = builder.mFieldsDetection;
+ mFieldClassificationIds = builder.mFieldClassificationIds;
mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -135,8 +135,8 @@ public final class FillResponse implements Parcelable {
}
/** @hide */
- public @Nullable FieldsDetection getFieldsDetection() {
- return mFieldsDetection;
+ public @Nullable AutofillId[] getFieldClassificationIds() {
+ return mFieldClassificationIds;
}
/** @hide */
@@ -175,7 +175,7 @@ public final class FillResponse implements Parcelable {
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
private long mDisableDuration;
- private FieldsDetection mFieldsDetection;
+ private AutofillId[] mFieldClassificationIds;
private int mFlags;
private boolean mDestroyed;
@@ -329,21 +329,29 @@ public final class FillResponse implements Parcelable {
}
/**
+ * Sets which fields are used for <a href="#FieldsClassification">fields classification</a>
+ *
+ * @throws IllegalArgumentException is length of {@code ids} args is more than
+ * {@link UserData#getMaxFieldClassificationIdsSize()}.
+ * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was
+ * already called.
+ * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
+ *
* TODO(b/67867469):
- * - javadoc it
- * - javadoc how to check results
- * - unhide
+ * - improve javadoc: explain relationship with UserData and how to check results
* - unhide / remove testApi
- * - throw exception (and document) if response has datasets or saveinfo
- * - throw exception (and document) if id on fieldsDetection is ignored
+ * - implement multiple ids
*
* @hide
*/
@TestApi
- public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) {
+ public Builder setFieldClassificationIds(@NonNull AutofillId... ids) {
throwIfDestroyed();
throwIfDisableAutofillCalled();
- mFieldsDetection = Preconditions.checkNotNull(fieldsDetection);
+ Preconditions.checkArrayElementsNotNull(ids, "ids");
+ Preconditions.checkArgumentInRange(ids.length, 1,
+ UserData.getMaxFieldClassificationIdsSize(), "ids length");
+ mFieldClassificationIds = ids;
return this;
}
@@ -391,16 +399,17 @@ public final class FillResponse implements Parcelable {
* @throws IllegalArgumentException if {@code duration} is not a positive number.
* @throws IllegalStateException if either {@link #addDataset(Dataset)},
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
- * {@link #setSaveInfo(SaveInfo)}, or {@link #setClientState(Bundle)}
- * was already called.
+ * {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
+ * {link #setFieldClassificationIds(AutofillId...)} was already called.
*/
+ // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
public Builder disableAutofill(long duration) {
throwIfDestroyed();
if (duration <= 0) {
throw new IllegalArgumentException("duration must be greater than 0");
}
if (mAuthentication != null || mDatasets != null || mSaveInfo != null
- || mFieldsDetection != null || mClientState != null) {
+ || mFieldClassificationIds != null || mClientState != null) {
throw new IllegalStateException("disableAutofill() must be the only method called");
}
@@ -417,15 +426,18 @@ public final class FillResponse implements Parcelable {
* <li>No call was made to {@link #addDataset(Dataset)},
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
* {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
- * or {@link #setClientState(Bundle)}.
+ * {@link #setClientState(Bundle)},
+ * or {link #setFieldClassificationIds(AutofillId...)}.
* </ol>
*
* @return A built response.
*/
+ // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
public FillResponse build() {
throwIfDestroyed();
if (mAuthentication == null && mDatasets == null && mSaveInfo == null
- && mDisableDuration == 0 && mFieldsDetection == null && mClientState == null) {
+ && mDisableDuration == 0 && mFieldClassificationIds == null
+ && mClientState == null) {
throw new IllegalStateException("need to provide: at least one DataSet, or a "
+ "SaveInfo, or an authentication with a presentation, "
+ "or a FieldsDetection, or a client state, or disable autofill");
@@ -466,7 +478,8 @@ public final class FillResponse implements Parcelable {
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
.append(", disableDuration=").append(mDisableDuration)
.append(", flags=").append(mFlags)
- .append(", fieldDetection=").append(mFieldsDetection)
+ .append(", fieldClassificationIds=")
+ .append(Arrays.toString(mFieldClassificationIds))
.append("]")
.toString();
}
@@ -490,7 +503,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
parcel.writeLong(mDisableDuration);
- parcel.writeParcelable(mFieldsDetection, flags);
+ parcel.writeParcelableArray(mFieldClassificationIds, flags);
parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -526,9 +539,10 @@ public final class FillResponse implements Parcelable {
if (disableDuration > 0) {
builder.disableAutofill(disableDuration);
}
- final FieldsDetection fieldsDetection = parcel.readParcelable(null);
- if (fieldsDetection != null) {
- builder.setFieldsDetection(fieldsDetection);
+ final AutofillId[] fieldClassifactionIds =
+ parcel.readParcelableArray(null, AutofillId.class);
+ if (fieldClassifactionIds != null) {
+ builder.setFieldClassificationIds(fieldClassifactionIds);
}
builder.setFlags(parcel.readInt());
diff --git a/core/java/android/service/autofill/UserData.aidl b/core/java/android/service/autofill/UserData.aidl
new file mode 100644
index 000000000000..76016ded424a
--- /dev/null
+++ b/core/java/android/service/autofill/UserData.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 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;
+
+parcelable UserData;
+parcelable UserData.Constraints;
diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java
new file mode 100644
index 000000000000..16d8d4ad3d86
--- /dev/null
+++ b/core/java/android/service/autofill/UserData.java
@@ -0,0 +1,288 @@
+/*
+ * 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.annotation.TestApi;
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.Helper;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Class used by service to improve autofillable fields detection by tracking the meaning of fields
+ * manually edited by the user (when they match values provided by the service).
+ *
+ * TODO(b/67867469):
+ * - improve javadoc / add link to section on AutofillService
+ * - unhide / remove testApi
+ * @hide
+ */
+@TestApi
+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 String[] mRemoteIds;
+ private final String[] mValues;
+
+ private UserData(Builder builder) {
+ mRemoteIds = new String[builder.mRemoteIds.size()];
+ builder.mRemoteIds.toArray(mRemoteIds);
+ mValues = new String[builder.mValues.size()];
+ builder.mValues.toArray(mValues);
+ }
+
+ /** @hide */
+ public String[] getRemoteIds() {
+ return mRemoteIds;
+ }
+
+ /** @hide */
+ public String[] getValues() {
+ return mValues;
+ }
+
+ /** @hide */
+ public void dump(String prefix, PrintWriter pw) {
+ // Cannot disclose remote ids because they could contain PII
+ pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length);
+ for (int i = 0; i < mValues.length; i++) {
+ pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); pw.println(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.
+ *
+ * TODO(b/67867469): unhide / remove testApi
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Builder {
+ private final ArraySet<String> mRemoteIds;
+ private final ArrayList<String> mValues;
+ private boolean mDestroyed;
+
+ /**
+ * Creates a new builder for the user data used for <a href="#FieldsClassification">fields
+ * classification</a>.
+ *
+ * @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(@NonNull String remoteId, @NonNull String value) {
+ checkValidRemoteId(remoteId);
+ checkValidValue(value);
+ final int capacity = getMaxUserDataSize();
+ mRemoteIds = new ArraySet<>(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(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();
+
+ // Cannot disclose keys or values because they could contain PII
+ final StringBuilder builder = new StringBuilder("UserData: [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.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 String[] remoteIds = parcel.readStringArray();
+ final String[] values = parcel.readStringArray();
+ final Builder builder = new Builder(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 {@link Builder#Builder(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 {@link Builder#Builder(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);
+ }
+}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 547e0db9e841..9a99e5398c8e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -36,6 +37,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -1007,6 +1009,54 @@ public final class AutofillManager {
}
/**
+ * Gets the user data used for <a href="#FieldsClassification">fields classification</a>.
+ *
+ * <p><b>Note:</b> This method should only be called by an app providing an autofill service.
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - unhide / remove testApi
+ *
+ * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
+ * reset or if the caller currently does not have an enabled autofill service for the user.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable public UserData getUserData() {
+ try {
+ return mService.getUserData();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Sets the user data used for <a href="#FieldsClassification">fields classification</a>.
+ *
+ * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+ * and it's ignored if the caller currently doesn't have an enabled autofill service for
+ * the user.
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - unhide / remove testApi
+ * - add unit tests:
+ * - call set / get / verify
+ *
+ * @hide
+ */
+ @TestApi
+ public void setUserData(@Nullable UserData userData) {
+ try {
+ mService.setUserData(userData);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns {@code true} if autofill is supported by the current device and
* is supported for this user.
*
diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java
index 3beae11cf38c..8e649de52c97 100644
--- a/core/java/android/view/autofill/AutofillValue.java
+++ b/core/java/android/view/autofill/AutofillValue.java
@@ -177,7 +177,7 @@ public final class AutofillValue implements Parcelable {
.append("[type=").append(mType)
.append(", value=");
if (isText()) {
- string.append(((CharSequence) mValue).length()).append("_chars");
+ Helper.appendRedacted(string, (CharSequence) mValue);
} else {
string.append(mValue);
}
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 829e7f3aa5ac..b95704af7d44 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -16,6 +16,8 @@
package android.view.autofill;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Bundle;
import java.util.Arrays;
@@ -50,6 +52,35 @@ public final class Helper {
return builder;
}
+ /**
+ * Appends {@code value} to the {@code builder} redacting its contents.
+ */
+ public static void appendRedacted(@NonNull StringBuilder builder,
+ @Nullable CharSequence value) {
+ if (value == null) {
+ builder.append("null");
+ } else {
+ builder.append(value.length()).append("_chars");
+ }
+ }
+
+ /**
+ * Appends {@code values} to the {@code builder} redacting its contents.
+ */
+ public static void appendRedacted(@NonNull StringBuilder builder, @Nullable String[] values) {
+ if (values == null) {
+ builder.append("N/A");
+ return;
+ }
+ builder.append("[");
+ for (String value : values) {
+ builder.append(" '");
+ appendRedacted(builder, value);
+ builder.append("'");
+ }
+ builder.append(" ]");
+ }
+
private Helper() {
throw new UnsupportedOperationException("contains static members only");
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index d6db3fe573f5..7d6a19f529ce 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -21,6 +21,7 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
@@ -53,4 +54,6 @@ interface IAutoFillManager {
boolean isServiceSupported(int userId);
boolean isServiceEnabled(int userId, String packageName);
void onPendingSaveUi(int operation, IBinder token);
+ UserData getUserData();
+ void setUserData(in UserData userData);
}