diff options
| author | TreeHugger Robot <treehugger-gerrit@google.com> | 2017-11-08 19:13:39 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-11-08 19:13:39 +0000 |
| commit | 4a3d844742fca89c142ee24a469830d435d64689 (patch) | |
| tree | a0aabb96e4f835bafe9d829790051e67f41b781a /core/java | |
| parent | 2b267dfbe967661879b54c638e1f72ab85c5b2f5 (diff) | |
| parent | 24d7173cf1e3193bb48c9c6aed0261efe34f890b (diff) | |
Merge "Very initial field detection prototype."
Diffstat (limited to 'core/java')
| -rwxr-xr-x | core/java/android/provider/Settings.java | 9 | ||||
| -rw-r--r-- | core/java/android/service/autofill/FieldsDetection.java | 127 | ||||
| -rw-r--r-- | core/java/android/service/autofill/FillEventHistory.java | 57 | ||||
| -rw-r--r-- | core/java/android/service/autofill/FillResponse.java | 45 |
4 files changed, 230 insertions, 8 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0a20c4348462..171630552388 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5317,6 +5317,15 @@ public final class Settings { public static final String AUTOFILL_SERVICE = "autofill_service"; /** + * Experimental autofill feature. + * + * <p>TODO(b/67867469): remove once feature is finished + * @hide + */ + @TestApi + public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection"; + + /** * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead */ @Deprecated diff --git a/core/java/android/service/autofill/FieldsDetection.java b/core/java/android/service/autofill/FieldsDetection.java new file mode 100644 index 000000000000..550ecf687349 --- /dev/null +++ b/core/java/android/service/autofill/FieldsDetection.java @@ -0,0 +1,127 @@ +/* + * 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 b1857b303b36..736d9ef48d04 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -19,6 +19,7 @@ package android.service.autofill; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; @@ -164,6 +165,10 @@ public final class FillEventHistory implements Parcelable { dest.writeStringList(event.mManuallyFilledDatasetIds.get(j)); } } + dest.writeString(event.mDetectedRemoteId); + if (event.mDetectedRemoteId != null) { + dest.writeInt(event.mDetectedFieldScore); + } } } } @@ -226,6 +231,7 @@ public final class FillEventHistory implements Parcelable { * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill * contexts. */ + // TODO(b/67867469): update with field detection behavior public static final int TYPE_CONTEXT_COMMITTED = 4; /** @hide */ @@ -253,6 +259,9 @@ public final class FillEventHistory implements Parcelable { @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds; @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds; + @Nullable private final String mDetectedRemoteId; + private final int mDetectedFieldScore; + /** * Returns the type of the event. * @@ -355,6 +364,39 @@ public final class FillEventHistory implements Parcelable { } /** + * Gets the results of the last {@link FieldsDetection} 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 + * 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}. + * + * TODO(b/67867469): + * - improve javadoc + * - refine score meaning (for example, should 1 be different of -1?) + * - mention when it's set + * - unhide + * - unhide / remove testApi + * - add @NonNull / check it / add unit tests + * + * @hide + */ + @TestApi + @NonNull public Map<String, Integer> getDetectedFields() { + if (mDetectedRemoteId == null || mDetectedFieldScore == -1) { + return Collections.emptyMap(); + } + + final ArrayMap<String, Integer> map = new ArrayMap<>(1); + map.put(mDetectedRemoteId, mDetectedFieldScore); + return map; + } + + /** * Returns which fields were available on datasets provided by the service but manually * entered by the user. * @@ -430,7 +472,6 @@ public final class FillEventHistory implements Parcelable { * and belonged to datasets. * @param manuallyFilledDatasetIds The ids of datasets that had values matching the * respective entry on {@code manuallyFilledFieldIds}. - * * @throws IllegalArgumentException If the length of {@code changedFieldIds} and * {@code changedDatasetIds} doesn't match. * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and @@ -438,13 +479,15 @@ public final class FillEventHistory implements Parcelable { * * @hide */ + // TODO(b/67867469): document detection field parameters once stable public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, @Nullable List<String> selectedDatasetIds, @Nullable ArraySet<String> ignoredDatasetIds, @Nullable ArrayList<AutofillId> changedFieldIds, @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, - @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) { + @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, + @Nullable String detectedRemoteId, int detectedFieldScore) { mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED, "eventType"); mDatasetId = datasetId; @@ -467,6 +510,8 @@ public final class FillEventHistory implements Parcelable { } mManuallyFilledFieldIds = manuallyFilledFieldIds; mManuallyFilledDatasetIds = manuallyFilledDatasetIds; + mDetectedRemoteId = detectedRemoteId; + mDetectedFieldScore = detectedFieldScore; } @Override @@ -479,6 +524,8 @@ public final class FillEventHistory implements Parcelable { + ", changedDatasetsIds=" + mChangedDatasetIds + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds + + ", detectedRemoteId=" + mDetectedRemoteId + + ", detectedFieldScore=" + mDetectedFieldScore + "]"; } } @@ -514,11 +561,15 @@ public final class FillEventHistory implements Parcelable { } else { manuallyFilledDatasetIds = null; } + final String detectedRemoteId = parcel.readString(); + final int detectedFieldScore = detectedRemoteId == null ? -1 + : parcel.readInt(); selection.addEvent(new Event(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, - manuallyFilledFieldIds, manuallyFilledDatasetIds)); + manuallyFilledFieldIds, manuallyFilledDatasetIds, + detectedRemoteId, detectedFieldScore)); } return selection; } diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 2f6342af2b3b..4e6a88457484 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -22,6 +22,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.Activity; import android.content.IntentSender; import android.content.pm.ParceledListSlice; @@ -75,6 +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 int mFlags; private int mRequestId; @@ -87,6 +89,7 @@ public final class FillResponse implements Parcelable { mAuthenticationIds = builder.mAuthenticationIds; mIgnoredIds = builder.mIgnoredIds; mDisableDuration = builder.mDisableDuration; + mFieldsDetection = builder.mFieldsDetection; mFlags = builder.mFlags; mRequestId = INVALID_REQUEST_ID; } @@ -132,6 +135,11 @@ public final class FillResponse implements Parcelable { } /** @hide */ + public @Nullable FieldsDetection getFieldsDetection() { + return mFieldsDetection; + } + + /** @hide */ public int getFlags() { return mFlags; } @@ -167,6 +175,7 @@ public final class FillResponse implements Parcelable { private AutofillId[] mAuthenticationIds; private AutofillId[] mIgnoredIds; private long mDisableDuration; + private FieldsDetection mFieldsDetection; private int mFlags; private boolean mDestroyed; @@ -315,6 +324,25 @@ public final class FillResponse implements Parcelable { } /** + * TODO(b/67867469): + * - javadoc it + * - javadoc how to check results + * - unhide + * - unhide / remove testApi + * - throw exception (and document) if response has datasets or saveinfo + * - throw exception (and document) if id on fieldsDetection is ignored + * + * @hide + */ + @TestApi + public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) { + throwIfDestroyed(); + throwIfDisableAutofillCalled(); + mFieldsDetection = Preconditions.checkNotNull(fieldsDetection); + return this; + } + + /** * Sets flags changing the response behavior. * * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and @@ -365,7 +393,8 @@ public final class FillResponse implements Parcelable { if (duration <= 0) { throw new IllegalArgumentException("duration must be greater than 0"); } - if (mAuthentication != null || mDatasets != null || mSaveInfo != null) { + if (mAuthentication != null || mDatasets != null || mSaveInfo != null + || mFieldsDetection != null) { throw new IllegalStateException("disableAutofill() must be the only method called"); } @@ -388,11 +417,11 @@ public final class FillResponse implements Parcelable { */ public FillResponse build() { throwIfDestroyed(); - if (mAuthentication == null && mDatasets == null && mSaveInfo == null - && mDisableDuration == 0) { - throw new IllegalStateException("need to provide at least one DataSet or a " - + "SaveInfo or an authentication with a presentation or disable autofill"); + && mDisableDuration == 0 && mFieldsDetection == null) { + throw new IllegalStateException("need to provide: at least one DataSet, or a " + + "SaveInfo, or an authentication with a presentation, " + + "or a FieldsDetection, or disable autofill"); } mDestroyed = true; return new FillResponse(this); @@ -430,6 +459,7 @@ 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("]") .toString(); } @@ -453,6 +483,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mPresentation, flags); parcel.writeParcelableArray(mIgnoredIds, flags); parcel.writeLong(mDisableDuration); + parcel.writeParcelable(mFieldsDetection, flags); parcel.writeInt(mFlags); parcel.writeInt(mRequestId); } @@ -488,6 +519,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); + } builder.setFlags(parcel.readInt()); final FillResponse response = builder.build(); |
