}
public final class ImageTransformation implements android.os.Parcelable {
+ method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.ImageTransformation> CREATOR;
}
public static class ImageTransformation.Builder {
- ctor public ImageTransformation.Builder(android.view.autofill.AutofillId);
+ ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.lang.String, int);
method public android.service.autofill.ImageTransformation.Builder addOption(java.lang.String, int);
method public android.service.autofill.ImageTransformation build();
}
method public static android.service.autofill.Validator or(android.service.autofill.Validator...);
}
+ public abstract interface ValueFinder {
+ method public abstract java.lang.String findByAutofillId(android.view.autofill.AutofillId);
+ }
+
}
package android.service.carrier {
package android.view.autofill {
public final class AutofillId implements android.os.Parcelable {
+ ctor public AutofillId(int);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillId> CREATOR;
import static android.view.autofill.Helper.sDebug;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import com.android.internal.util.Preconditions;
+import java.util.regex.Pattern;
+
/**
* Replaces the content of a child {@link ImageView} of a
* {@link RemoteViews presentation template} with the first image that matches a regular expression
* <p>Typically used to display credit card logos. Example:
*
* <pre class="prettyprint">
- * new ImageTransformation.Builder(ccNumberId)
- * .addOption("^4815.*$", R.drawable.ic_credit_card_logo1)
+ * new ImageTransformation.Builder(ccNumberId, "^4815.*$", R.drawable.ic_credit_card_logo1)
* .addOption("^1623.*$", R.drawable.ic_credit_card_logo2)
* .addOption("^42.*$", R.drawable.ic_credit_card_logo3)
* .build();
* </pre>
*
* <p>There is no imposed limit in the number of options, but keep in mind that regexs are
- * expensive to evaluate, so try to:
- * <ul>
- * <li>Use the minimum number of regex per image.
- * <li>Add the most common images first.
- * </ul>
+ * expensive to evaluate, so use the minimum number of regexs.
*/
-//TODO(b/62534917): add unit tests
public final class ImageTransformation extends InternalTransformation implements Parcelable {
private static final String TAG = "ImageTransformation";
private final AutofillId mId;
- private final ArrayMap<String, Integer> mOptions;
+ private final ArrayMap<Pattern, Integer> mOptions;
private ImageTransformation(Builder builder) {
mId = builder.mId;
}
/** @hide */
+ @TestApi
@Override
public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate,
int childViewId) {
}
for (int i = 0; i < size; i++) {
- final String regex = mOptions.keyAt(i);
- if (value.matches(regex)) {
+ final Pattern regex = mOptions.keyAt(i);
+ if (regex.matcher(value).matches()) {
Log.d(TAG, "Found match at " + i + ": " + regex);
parentTemplate.setImageViewResource(childViewId, mOptions.valueAt(i));
return;
*/
public static class Builder {
private final AutofillId mId;
- private ArrayMap<String, Integer> mOptions;
+ private final ArrayMap<Pattern, Integer> mOptions = new ArrayMap<>();
private boolean mDestroyed;
/**
- * Default constructor.
+ * Create a new builder for a autofill id and add a first option.
*
* @param id id of the screen field that will be used to evaluate whether the image should
* be used.
+ * @param regex regular expression defining what should be matched to use this image.
+ * @param resId resource id of the image (in the autofill service's package). The
+ * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
*/
- //TODO(b/62534917): add a regex/resid so we force it to have at least one
- // (and then remove the check for empty from build())
- public Builder(@NonNull AutofillId id) {
+ public Builder(@NonNull AutofillId id, @NonNull String regex, @DrawableRes int resId) {
mId = Preconditions.checkNotNull(id);
+
+ addOption(regex, resId);
}
/**
*
* @return this build
*/
- public Builder addOption(String regex, int resId) {
- //TODO(b/62534917): throw exception if regex / resId are invalid
+ public Builder addOption(@NonNull String regex, @DrawableRes int resId) {
throwIfDestroyed();
- if (mOptions == null) {
- mOptions = new ArrayMap<>();
- }
- mOptions.put(regex, resId);
+
+ Preconditions.checkArgument(resId != 0);
+
+ // Check regex
+ Pattern pattern = Pattern.compile(regex);
+
+ mOptions.put(pattern, resId);
return this;
}
public int describeContents() {
return 0;
}
-
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mId, flags);
- if (mOptions == null) {
- parcel.writeStringArray(null);
- return;
- }
+
final int size = mOptions.size();
final String[] regexs = new String[size];
final int[] resIds = new int[size];
for (int i = 0; i < size; i++) {
- regexs[i] = mOptions.keyAt(i);
+ regexs[i] = mOptions.keyAt(i).pattern();
resIds[i] = mOptions.valueAt(i);
}
parcel.writeStringArray(regexs);
new Parcelable.Creator<ImageTransformation>() {
@Override
public ImageTransformation 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 ImageTransformation.Builder builder =
- new ImageTransformation.Builder(parcel.readParcelable(null));
+ final AutofillId id = parcel.readParcelable(null);
+
final String[] regexs = parcel.createStringArray();
- if (regexs != null) {
- final int[] resIds = parcel.createIntArray();
- final int size = regexs.length;
- for (int i = 0; i < size; i++) {
- builder.addOption(regexs[i], resIds[i]);
- }
+ final int[] resIds = parcel.createIntArray();
+
+ // 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 ImageTransformation.Builder builder = new ImageTransformation.Builder(id,
+ regexs[0], resIds[0]);
+
+ final int size = regexs.length;
+ for (int i = 1; i < size; i++) {
+ builder.addOption(regexs[i], resIds[i]);
}
+
return builder.build();
}