diff options
| author | 2018-01-04 19:00:40 +0000 | |
|---|---|---|
| committer | 2018-01-04 19:00:40 +0000 | |
| commit | 1e8a6d36bcc99aed82f9d788750f103f8b74fa43 (patch) | |
| tree | 4d8cc8bc9e10ba6d10addc5b86a7c65842ee24ca | |
| parent | fe214f5cc8a5e7a6fb3537ad6048a1078137e84d (diff) | |
| parent | 27f4573d136949abeacb00f7246ff9911e9cb105 (diff) | |
Merge "Refactored Field Classification score logic so it can be moved to ExtServices."
| -rw-r--r-- | api/current.txt | 17 | ||||
| -rw-r--r-- | api/test-current.txt | 8 | ||||
| -rw-r--r-- | core/java/android/service/autofill/EditDistanceScorer.java | 67 | ||||
| -rw-r--r-- | core/java/android/service/autofill/FieldClassification.aidl (renamed from core/java/android/service/autofill/Scorer.java) | 19 | ||||
| -rw-r--r-- | core/java/android/service/autofill/FieldClassification.java | 33 | ||||
| -rw-r--r-- | core/java/android/service/autofill/InternalScorer.java | 40 | ||||
| -rw-r--r-- | core/java/android/service/autofill/UserData.aidl | 1 | ||||
| -rw-r--r-- | core/java/android/service/autofill/UserData.java | 69 | ||||
| -rw-r--r-- | core/java/android/view/autofill/AutofillManager.java | 48 | ||||
| -rw-r--r-- | core/java/android/view/autofill/IAutoFillManager.aidl | 2 | ||||
| -rw-r--r-- | services/autofill/java/com/android/server/autofill/AutofillManagerService.java | 30 | ||||
| -rw-r--r-- | services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java | 124 | ||||
| -rw-r--r-- | services/autofill/java/com/android/server/autofill/Session.java | 18 |
13 files changed, 313 insertions, 163 deletions
diff --git a/api/current.txt b/api/current.txt index 10b5c6785065..32deabb97850 100644 --- a/api/current.txt +++ b/api/current.txt @@ -37728,18 +37728,12 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } - public final class EditDistanceScorer implements android.os.Parcelable android.service.autofill.Scorer { - method public int describeContents(); - method public static android.service.autofill.EditDistanceScorer getInstance(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR; - } - public final class FieldClassification { method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches(); } public static final class FieldClassification.Match { + method public java.lang.String getAlgorithm(); method public java.lang.String getRemoteId(); method public float getScore(); } @@ -37891,9 +37885,6 @@ package android.service.autofill { field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR; } - public abstract interface Scorer { - } - public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer { ctor public TextValueSanitizer(java.util.regex.Pattern, java.lang.String); method public int describeContents(); @@ -37906,6 +37897,7 @@ package android.service.autofill { public final class UserData implements android.os.Parcelable { method public int describeContents(); + method public java.lang.String getFieldClassificationAlgorithm(); method public static int getMaxFieldClassificationIdsSize(); method public static int getMaxUserDataSize(); method public static int getMaxValueLength(); @@ -37915,9 +37907,10 @@ package android.service.autofill { } public static final class UserData.Builder { - ctor public UserData.Builder(android.service.autofill.Scorer, java.lang.String, java.lang.String); + ctor public UserData.Builder(java.lang.String, java.lang.String); method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String); method public android.service.autofill.UserData build(); + method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithm(java.lang.String, android.os.Bundle); } public abstract interface Validator { @@ -48550,6 +48543,8 @@ package android.view.autofill { method public void commit(); method public void disableAutofillServices(); method public android.content.ComponentName getAutofillServiceComponentName(); + method public java.util.List<java.lang.String> getAvailableFieldClassificationAlgorithms(); + method public java.lang.String getDefaultFieldClassificationAlgorithm(); method public android.service.autofill.UserData getUserData(); method public boolean hasEnabledAutofillServices(); method public boolean isAutofillSupported(); diff --git a/api/test-current.txt b/api/test-current.txt index d56b0856c028..a0f981a7b5a9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -473,7 +473,8 @@ package android.service.autofill { method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception; } - public final class EditDistanceScorer extends android.service.autofill.InternalScorer implements android.os.Parcelable android.service.autofill.Scorer { + public final class EditDistanceScorer { + method public static android.service.autofill.EditDistanceScorer getInstance(); method public float getScore(android.view.autofill.AutofillValue, java.lang.String); } @@ -489,11 +490,6 @@ package android.service.autofill { ctor public InternalSanitizer(); } - public abstract class InternalScorer implements android.os.Parcelable android.service.autofill.Scorer { - ctor public InternalScorer(); - method public abstract float getScore(android.view.autofill.AutofillValue, java.lang.String); - } - public abstract class InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation { ctor public InternalTransformation(); } diff --git a/core/java/android/service/autofill/EditDistanceScorer.java b/core/java/android/service/autofill/EditDistanceScorer.java index 0706b377bbe8..97a386866665 100644 --- a/core/java/android/service/autofill/EditDistanceScorer.java +++ b/core/java/android/service/autofill/EditDistanceScorer.java @@ -16,8 +16,7 @@ package android.service.autofill; import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; +import android.annotation.TestApi; import android.view.autofill.AutofillValue; /** @@ -25,13 +24,20 @@ import android.view.autofill.AutofillValue; * by the user and the expected value predicted by an autofill service. */ // TODO(b/70291841): explain algorithm once it's fully implemented -public final class EditDistanceScorer extends InternalScorer implements Scorer, Parcelable { +/** @hide */ +@TestApi +public final class EditDistanceScorer { private static final EditDistanceScorer sInstance = new EditDistanceScorer(); + /** @hide */ + public static final String NAME = "EDIT_DISTANCE"; + /** * Gets the singleton instance. */ + @TestApi + /** @hide */ public static EditDistanceScorer getInstance() { return sInstance; } @@ -39,59 +45,32 @@ public final class EditDistanceScorer extends InternalScorer implements Scorer, private EditDistanceScorer() { } - /** @hide */ - @Override - public float getScore(@NonNull AutofillValue actualValue, @NonNull String userData) { - if (actualValue == null || !actualValue.isText() || userData == null) return 0; + /** + * Returns the classification score between an actual {@link AutofillValue} filled + * by the user and the expected value predicted by an autofill service. + * + * <p>A full-match is {@code 1.0} (representing 100%), a full mismatch is {@code 0.0} and + * partial mathces are something in between, typically using edit-distance algorithms. + * + * @hide + */ + @TestApi + public float getScore(@NonNull AutofillValue actualValue, @NonNull String userDataValue) { + if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0; // TODO(b/70291841): implement edit distance - currently it's returning either 0, 100%, or // partial match when number of chars match final String textValue = actualValue.getTextValue().toString(); final int total = textValue.length(); - if (total != userData.length()) return 0F; + if (total != userDataValue.length()) return 0F; int matches = 0; for (int i = 0; i < total; i++) { if (Character.toLowerCase(textValue.charAt(i)) == Character - .toLowerCase(userData.charAt(i))) { + .toLowerCase(userDataValue.charAt(i))) { matches++; } } return ((float) matches) / total; } - - ///////////////////////////////////// - // Object "contract" methods. // - ///////////////////////////////////// - @Override - public String toString() { - return "EditDistanceScorer"; - } - - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - // Do nothing - } - - public static final Parcelable.Creator<EditDistanceScorer> CREATOR = - new Parcelable.Creator<EditDistanceScorer>() { - @Override - public EditDistanceScorer createFromParcel(Parcel parcel) { - return EditDistanceScorer.getInstance(); - } - - @Override - public EditDistanceScorer[] newArray(int size) { - return new EditDistanceScorer[size]; - } - }; } diff --git a/core/java/android/service/autofill/Scorer.java b/core/java/android/service/autofill/FieldClassification.aidl index c4018558b3ed..42f7f0252d6d 100644 --- a/core/java/android/service/autofill/Scorer.java +++ b/core/java/android/service/autofill/FieldClassification.aidl @@ -1,11 +1,11 @@ -/* - * Copyright (C) 2017 The Android Open Source Project +/** + * 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 + * 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, @@ -13,16 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.autofill; -/** - * Helper class used to calculate a score. - * - * <p>Typically used to calculate the - * <a href="AutofillService.html#FieldClassification">field classification</a> score between an - * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value - * predicted by an autofill service. - */ -public interface Scorer { +package android.service.autofill; -} +parcelable FieldClassification.AlgorithmInfo; diff --git a/core/java/android/service/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java index 001b2917aadf..536180335a83 100644 --- a/core/java/android/service/autofill/FieldClassification.java +++ b/core/java/android/service/autofill/FieldClassification.java @@ -106,18 +106,20 @@ public final class FieldClassification { /** * Represents the score of a {@link UserData} entry for the field. * - * <p>The score is defined by {@link #getScore()} and the entry is identified by - * {@link #getRemoteId()}. + * <p>The score is calculated by the given {@link #getAlgorithm() algorithm} and + * the entry is identified by {@link #getRemoteId()}. */ public static final class Match { private final String mRemoteId; private final float mScore; + private final String mAlgorithm; /** @hide */ - public Match(String remoteId, float score) { + public Match(String remoteId, float score, String algorithm) { mRemoteId = Preconditions.checkNotNull(remoteId); mScore = score; + mAlgorithm = algorithm; } /** @@ -140,29 +142,46 @@ public final class FieldClassification { * <li>Any other value is a partial match. * </ul> * - * <p>How the score is calculated depends on the algorithm used by the {@link Scorer} - * implementation. + * <p>How the score is calculated depends on the + * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle) + * algorithm} used. */ public float getScore() { return mScore; } + /** + * Gets the algorithm used to calculate this score. + * + * <p>Typically, this is either the algorithm set by + * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle)}, + * or the + * {@link android.view.autofill.AutofillManager#getDefaultFieldClassificationAlgorithm()}. + */ + @NonNull + public String getAlgorithm() { + return mAlgorithm; + } + @Override public String toString() { if (!sDebug) return super.toString(); final StringBuilder string = new StringBuilder("Match: remoteId="); Helper.appendRedacted(string, mRemoteId); - return string.append(", score=").append(mScore).toString(); + return string.append(", score=").append(mScore) + .append(", algorithm=").append(mAlgorithm) + .toString(); } private void writeToParcel(@NonNull Parcel parcel) { parcel.writeString(mRemoteId); parcel.writeFloat(mScore); + parcel.writeString(mAlgorithm); } private static Match readFromParcel(@NonNull Parcel parcel) { - return new Match(parcel.readString(), parcel.readFloat()); + return new Match(parcel.readString(), parcel.readFloat(), parcel.readString()); } } } diff --git a/core/java/android/service/autofill/InternalScorer.java b/core/java/android/service/autofill/InternalScorer.java deleted file mode 100644 index 0da5afc2331d..000000000000 --- a/core/java/android/service/autofill/InternalScorer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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; - -import android.annotation.NonNull; -import android.annotation.TestApi; -import android.os.Parcelable; -import android.view.autofill.AutofillValue; - -/** - * Superclass of all scorer the system understands. As this is not public all - * subclasses have to implement {@link Scorer} again. - * - * @hide - */ -@TestApi -public abstract class InternalScorer implements Scorer, Parcelable { - - /** - * Returns the classification score between an actual {@link AutofillValue} filled - * by the user and the expected value predicted by an autofill service. - * - * <p>A full-match is {@code 1.0} (representing 100%), a full mismatch is {@code 0.0} and - * partial mathces are something in between, typically using edit-distance algorithms. - */ - public abstract float getScore(@NonNull AutofillValue actualValue, @NonNull String userData); -} diff --git a/core/java/android/service/autofill/UserData.aidl b/core/java/android/service/autofill/UserData.aidl index 76016ded424a..19282e0e7c85 100644 --- a/core/java/android/service/autofill/UserData.aidl +++ b/core/java/android/service/autofill/UserData.aidl @@ -17,4 +17,3 @@ 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 index f0cc360fe075..2f9225acc520 100644 --- a/core/java/android/service/autofill/UserData.java +++ b/core/java/android/service/autofill/UserData.java @@ -25,10 +25,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.content.ContentResolver; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.provider.Settings; +import android.service.autofill.FieldClassification.Match; import android.util.Log; +import android.view.autofill.AutofillManager; import android.view.autofill.Helper; import com.android.internal.util.Preconditions; @@ -49,21 +52,32 @@ public final class UserData implements Parcelable { 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 mAlgorithm; + private final Bundle mAlgorithmArgs; private final String[] mRemoteIds; private final String[] mValues; private UserData(Builder builder) { - mScorer = builder.mScorer; + mAlgorithm = builder.mAlgorithm; + mAlgorithmArgs = builder.mAlgorithmArgs; mRemoteIds = new String[builder.mRemoteIds.size()]; builder.mRemoteIds.toArray(mRemoteIds); mValues = new String[builder.mValues.size()]; builder.mValues.toArray(mValues); } + /** + * Gets the name of the algorithm that is used to calculate + * {@link Match#getScore() match scores}. + */ + @Nullable + public String getFieldClassificationAlgorithm() { + return mAlgorithm; + } + /** @hide */ - public InternalScorer getScorer() { - return mScorer; + public Bundle getAlgorithmArgs() { + return mAlgorithmArgs; } /** @hide */ @@ -78,7 +92,9 @@ public final class UserData implements Parcelable { /** @hide */ public void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("Scorer: "); pw.println(mScorer); + pw.print(prefix); pw.print("Algorithm: "); pw.print(mAlgorithm); + pw.print(" Args: "); pw.println(mAlgorithmArgs); + // 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++) { @@ -105,9 +121,10 @@ public final class UserData implements Parcelable { * 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 String mAlgorithm; + private Bundle mAlgorithmArgs; private boolean mDestroyed; /** @@ -120,13 +137,9 @@ public final class UserData implements Parcelable { * <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; + public Builder(@NonNull String remoteId, @NonNull String value) { checkValidRemoteId(remoteId); checkValidValue(value); final int capacity = getMaxUserDataSize(); @@ -137,6 +150,31 @@ public final class UserData implements Parcelable { } /** + * Sets the algorithm used for <a href="#FieldClassification">field classification</a>. + * + * <p>The currently available algorithms can be retrieve through + * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}. + * + * <p><b>Note: </b>The available algorithms in the Android System can change dinamically, + * so it's not guaranteed that the algorithm set here is the one that will be effectually + * used. If the algorithm set here is not available at runtime, the + * {@link AutofillManager#getDefaultFieldClassificationAlgorithm()} is used instead. + * You can verify which algorithm was used by calling + * {@link FieldClassification.Match#getAlgorithm()}. + * + * @param name name of the algorithm or {@code null} to used default. + * @param args optional arguments to the algorithm. + * + * @return this builder + */ + public Builder setFieldClassificationAlgorithm(@Nullable String name, + @Nullable Bundle args) { + mAlgorithm = name; + mAlgorithmArgs = args; + return this; + } + + /** * Adds a new value for user data. * * @param remoteId unique string used to identify the user data. @@ -211,7 +249,7 @@ public final class UserData implements Parcelable { public String toString() { if (!sDebug) return super.toString(); - final StringBuilder builder = new StringBuilder("UserData: [scorer=").append(mScorer); + final StringBuilder builder = new StringBuilder("UserData: [algorithm=").append(mAlgorithm); // Cannot disclose remote ids or values because they could contain PII builder.append(", remoteIds="); Helper.appendRedacted(builder, mRemoteIds); @@ -231,9 +269,10 @@ public final class UserData implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeParcelable(mScorer, flags); parcel.writeStringArray(mRemoteIds); parcel.writeStringArray(mValues); + parcel.writeString(mAlgorithm); + parcel.writeBundle(mAlgorithmArgs); } public static final Parcelable.Creator<UserData> CREATOR = @@ -243,10 +282,10 @@ public final class UserData implements Parcelable { // 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]); + final Builder builder = new Builder(remoteIds[0], values[0]) + .setFieldClassificationAlgorithm(parcel.readString(), parcel.readBundle()); for (int i = 1; i < remoteIds.length; i++) { builder.add(remoteIds[i], values[i]); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 269745455517..78b41c6f4c7b 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -53,6 +53,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -1027,7 +1028,9 @@ public final class AutofillManager { * Gets the user data used for * <a href="AutofillService.html#FieldClassification">field classification</a>. * - * <p><b>Note:</b> This method should only be called by an app providing an autofill service. + * <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. * * @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. @@ -1079,6 +1082,49 @@ public final class AutofillManager { } /** + * Gets the name of the default algorithm used for + * <a href="AutofillService.html#FieldClassification">field classification</a>. + * + * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not + * set. + * + * <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. + */ + @Nullable + public String getDefaultFieldClassificationAlgorithm() { + try { + return mService.getDefaultFieldClassificationAlgorithm(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Gets the name of all algorithms currently available for + * <a href="AutofillService.html#FieldClassification">field 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. + * + * @return list of all algorithms currently available, or an empty list if the caller currently + * does not have an enabled autofill service for the user. + */ + @NonNull + public List<String> getAvailableFieldClassificationAlgorithms() { + try { + final List<String> names = mService.getAvailableFieldClassificationAlgorithms(); + return names != null ? names : Collections.emptyList(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** * 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/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 38bb311986e8..1afa35e80a26 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -58,4 +58,6 @@ interface IAutoFillManager { void setUserData(in UserData userData); boolean isFieldClassificationEnabled(); ComponentName getAutofillServiceComponentName(); + List<String> getAvailableFieldClassificationAlgorithms(); + String getDefaultFieldClassificationAlgorithm(); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index e1cb154c88f5..cac7fedd0b00 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -479,7 +479,7 @@ public final class AutofillManagerService extends SystemService { if (service != null) { service.destroySessionsLocked(); service.updateLocked(disabled); - if (!service.isEnabled()) { + if (!service.isEnabledLocked()) { removeCachedServiceLocked(userId); } } @@ -621,6 +621,34 @@ public final class AutofillManagerService extends SystemService { } @Override + public String getDefaultFieldClassificationAlgorithm() throws RemoteException { + final int userId = UserHandle.getCallingUserId(); + + synchronized (mLock) { + final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); + if (service != null) { + return service.getDefaultFieldClassificationAlgorithm(getCallingUid()); + } + } + + return null; + } + + @Override + public List<String> getAvailableFieldClassificationAlgorithms() throws RemoteException { + final int userId = UserHandle.getCallingUserId(); + + synchronized (mLock) { + final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); + if (service != null) { + return service.getAvailableFieldClassificationAlgorithms(getCallingUid()); + } + } + + return null; + } + + @Override public ComponentName getAutofillServiceComponentName() throws RemoteException { final int userId = UserHandle.getCallingUserId(); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 4cdfd625a8f0..65984dd5fdbe 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -51,6 +51,7 @@ import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; +import android.service.autofill.EditDistanceScorer; import android.service.autofill.FieldClassification; import android.service.autofill.FieldClassification.Match; import android.service.autofill.FillEventHistory; @@ -63,6 +64,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -81,6 +84,7 @@ import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Random; @@ -105,7 +109,10 @@ final class AutofillManagerServiceImpl { private final AutoFillUI mUi; private final MetricsLogger mMetricsLogger = new MetricsLogger(); + @GuardedBy("mLock") private RemoteCallbackList<IAutoFillManagerClient> mClients; + + @GuardedBy("mLock") private AutofillServiceInfo mInfo; private static final Random sRandom = new Random(); @@ -113,25 +120,74 @@ final class AutofillManagerServiceImpl { private final LocalLog mRequestsHistory; private final LocalLog mUiLatencyHistory; + // TODO(b/70939974): temporary, will be moved to ExtServices + static final class FieldClassificationAlgorithmService { + + /** + * Gets the name of all available algorithms. + */ + @NonNull + public List<String> getAvailableAlgorithms() { + return Arrays.asList(EditDistanceScorer.NAME); + } + + /** + * Gets the default algorithm that's used when an algorithm is not specified or is invalid. + */ + @NonNull + public String getDefaultAlgorithm() { + return EditDistanceScorer.NAME; + } + + /** + * Gets a field classification score. + * + * @param algorithmName algorithm to be used. If invalid, the default algorithm will be used + * instead. + * @param algorithmArgs optional arguments to be passed to the algorithm. + * @param actualValue value entered by the user. + * @param userDataValue value from the user data. + * + * @return pair containing the algorithm used and the score. + */ + // TODO(b/70939974): use parcelable instead of pair + Pair<String, Float> getScore(@NonNull String algorithmName, @Nullable Bundle algorithmArgs, + @NonNull AutofillValue actualValue, @NonNull String userDataValue) { + if (!EditDistanceScorer.NAME.equals(algorithmName)) { + Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using " + + EditDistanceScorer.NAME + " instead"); + } + return new Pair<>(EditDistanceScorer.NAME, + EditDistanceScorer.getInstance().getScore(actualValue, userDataValue)); + } + } + + private final FieldClassificationAlgorithmService mFcService = + new FieldClassificationAlgorithmService(); + /** * Apps disabled by the service; key is package name, value is when they will be enabled again. */ + @GuardedBy("mLock") private ArrayMap<String, Long> mDisabledApps; /** * Activities disabled by the service; key is component name, value is when they will be enabled * again. */ + @GuardedBy("mLock") private ArrayMap<ComponentName, Long> mDisabledActivities; /** * Whether service was disabled for user due to {@link UserManager} restrictions. */ + @GuardedBy("mLock") private boolean mDisabled; /** * Data used for field classification. */ + @GuardedBy("mLock") private UserData mUserData; /** @@ -235,7 +291,7 @@ final class AutofillManagerServiceImpl { } void updateLocked(boolean disabled) { - final boolean wasEnabled = isEnabled(); + final boolean wasEnabled = isEnabledLocked(); if (sVerbose) { Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled + ", mSetupComplete= " + mSetupComplete @@ -274,7 +330,7 @@ final class AutofillManagerServiceImpl { Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e); mInfo = null; } - final boolean isEnabled = isEnabled(); + final boolean isEnabled = isEnabledLocked(); if (wasEnabled != isEnabled) { if (!isEnabled) { final int sessionCount = mSessions.size(); @@ -292,7 +348,7 @@ final class AutofillManagerServiceImpl { mClients = new RemoteCallbackList<>(); } mClients.register(client); - return isEnabled(); + return isEnabledLocked(); } void removeClientLocked(IAutoFillManagerClient client) { @@ -302,7 +358,7 @@ final class AutofillManagerServiceImpl { } void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) { - if (!isEnabled()) { + if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); @@ -312,7 +368,7 @@ final class AutofillManagerServiceImpl { } void setHasCallback(int sessionId, int uid, boolean hasIt) { - if (!isEnabled()) { + if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); @@ -327,7 +383,7 @@ final class AutofillManagerServiceImpl { @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, int flags, @NonNull ComponentName componentName) { - if (!isEnabled()) { + if (!isEnabledLocked()) { return 0; } @@ -388,7 +444,7 @@ final class AutofillManagerServiceImpl { } void finishSessionLocked(int sessionId, int uid) { - if (!isEnabled()) { + if (!isEnabledLocked()) { return; } @@ -411,7 +467,7 @@ final class AutofillManagerServiceImpl { } void cancelSessionLocked(int sessionId, int uid) { - if (!isEnabled()) { + if (!isEnabledLocked()) { return; } @@ -799,14 +855,15 @@ final class AutofillManagerServiceImpl { // Called by AutofillManager void setUserData(int callingUid, UserData userData) { synchronized (mLock) { - if (isCalledByServiceLocked("setUserData", callingUid)) { - mUserData = userData; - // Log it - int numberFields = mUserData == null ? 0: mUserData.getRemoteIds().length; - mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED, - getServicePackageName(), null) - .setCounterValue(numberFields)); + if (!isCalledByServiceLocked("setUserData", callingUid)) { + return; } + mUserData = userData; + // Log it + int numberFields = mUserData == null ? 0: mUserData.getRemoteIds().length; + mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED, + getServicePackageName(), null) + .setCounterValue(numberFields)); } } @@ -917,6 +974,11 @@ final class AutofillManagerServiceImpl { pw.println(); mUserData.dump(prefix2, pw); } + + pw.print(prefix); pw.print("Available Field Classification algorithms: "); + pw.println(mFcService.getAvailableAlgorithms()); + pw.print(prefix); pw.print("Default Field Classification algorithm: "); + pw.println(mFcService.getDefaultAlgorithm()); } void destroySessionsLocked() { @@ -964,11 +1026,13 @@ final class AutofillManagerServiceImpl { final IAutoFillManagerClient client = clients.getBroadcastItem(i); try { final boolean resetSession; + final boolean isEnabled; synchronized (mLock) { resetSession = resetClient || isClientSessionDestroyedLocked(client); + isEnabled = isEnabledLocked(); } int flags = 0; - if (isEnabled()) { + if (isEnabled) { flags |= AutofillManager.SET_STATE_FLAG_ENABLED; } if (resetSession) { @@ -1004,7 +1068,7 @@ final class AutofillManagerServiceImpl { return true; } - boolean isEnabled() { + boolean isEnabledLocked() { return mSetupComplete && mInfo != null && !mDisabled; } @@ -1093,9 +1157,9 @@ final class AutofillManagerServiceImpl { } // Called by AutofillManager, checks UID. - boolean isFieldClassificationEnabled(int uid) { + boolean isFieldClassificationEnabled(int callingUid) { synchronized (mLock) { - if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) { + if (!isCalledByServiceLocked("isFieldClassificationEnabled", callingUid)) { return false; } return isFieldClassificationEnabledLocked(); @@ -1110,6 +1174,28 @@ final class AutofillManagerServiceImpl { mUserId) == 1; } + FieldClassificationAlgorithmService getFieldClassificationService() { + return mFcService; + } + + List<String> getAvailableFieldClassificationAlgorithms(int callingUid) { + synchronized (mLock) { + if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) { + return null; + } + } + return mFcService.getAvailableAlgorithms(); + } + + String getDefaultFieldClassificationAlgorithm(int callingUid) { + synchronized (mLock) { + if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) { + return null; + } + } + return mFcService.getDefaultAlgorithm(); + } + @Override public String toString() { return "AutofillManagerServiceImpl: [userId=" + mUserId diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 01f908407691..96296907b2c5 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -69,6 +69,7 @@ import android.service.autofill.FieldClassification; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -84,6 +85,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.HandlerCaller; import com.android.internal.util.ArrayUtils; +import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.autofill.ui.PendingUi; @@ -1088,7 +1090,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sets field classification score for field if (userData!= null) { - setScore(detectedFieldIds, detectedFieldClassifications, userData, + setFieldClassificationScore(mService.getFieldClassificationService(), + detectedFieldIds, detectedFieldClassifications, userData, viewState.id, currentValue); } } // else @@ -1133,7 +1136,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for * {@code fieldId} based on its {@code currentValue} and {@code userData}. */ - private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds, + private static void setFieldClassificationScore( + @NonNull AutofillManagerServiceImpl.FieldClassificationAlgorithmService service, + @NonNull ArrayList<AutofillId> detectedFieldIds, @NonNull ArrayList<FieldClassification> detectedFieldClassifications, @NonNull UserData userData, @NonNull AutofillId fieldId, @NonNull AutofillValue currentValue) { @@ -1150,11 +1155,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } + final String algorithm = userData.getFieldClassificationAlgorithm(); + final Bundle algorithmArgs = userData.getAlgorithmArgs(); ArrayList<Match> matches = null; for (int i = 0; i < userValues.length; i++) { String remoteId = remoteIds[i]; final String value = userValues[i]; - final float score = userData.getScorer().getScore(currentValue, value); + final Pair<String, Float> result = service.getScore(algorithm, algorithmArgs, + currentValue, value); + final String actualAlgorithm = result.first; + final float score = result.second; if (score > 0) { if (sVerbose) { Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId); @@ -1162,7 +1172,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (matches == null) { matches = new ArrayList<>(userValues.length); } - matches.add(new Match(remoteId, score)); + matches.add(new Match(remoteId, score, actualAlgorithm)); } else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId); } |