ImageTransformation.java revision 7fc29dd9311cc36c3eb2a6a05aeed2d39ddcc604
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.service.autofill; 18 19import static android.view.autofill.Helper.sDebug; 20 21import android.annotation.DrawableRes; 22import android.annotation.NonNull; 23import android.annotation.TestApi; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.util.Log; 27import android.util.Pair; 28import android.view.autofill.AutofillId; 29import android.widget.ImageView; 30import android.widget.RemoteViews; 31 32import com.android.internal.util.Preconditions; 33 34import java.util.ArrayList; 35import java.util.regex.Pattern; 36 37/** 38 * Replaces the content of a child {@link ImageView} of a 39 * {@link RemoteViews presentation template} with the first image that matches a regular expression 40 * (regex). 41 * 42 * <p>Typically used to display credit card logos. Example: 43 * 44 * <pre class="prettyprint"> 45 * new ImageTransformation.Builder(ccNumberId, "^4815.*$", R.drawable.ic_credit_card_logo1) 46 * .addOption("^1623.*$", R.drawable.ic_credit_card_logo2) 47 * .addOption("^42.*$", R.drawable.ic_credit_card_logo3) 48 * .build(); 49 * </pre> 50 * 51 * <p>There is no imposed limit in the number of options, but keep in mind that regexs are 52 * expensive to evaluate, so use the minimum number of regexs and add the most common first 53 * (for example, if this is a tranformation for a credit card logo and the most common credit card 54 * issuers are banks X and Y, add the regexes that resolves these 2 banks first). 55 */ 56public final class ImageTransformation extends InternalTransformation implements Transformation, 57 Parcelable { 58 private static final String TAG = "ImageTransformation"; 59 60 private final AutofillId mId; 61 private final ArrayList<Pair<Pattern, Integer>> mOptions; 62 63 private ImageTransformation(Builder builder) { 64 mId = builder.mId; 65 mOptions = builder.mOptions; 66 } 67 68 /** @hide */ 69 @TestApi 70 @Override 71 public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate, 72 int childViewId) { 73 final String value = finder.findByAutofillId(mId); 74 if (value == null) { 75 Log.w(TAG, "No view for id " + mId); 76 return; 77 } 78 final int size = mOptions.size(); 79 if (sDebug) { 80 Log.d(TAG, size + " multiple options on id " + childViewId + " to compare against " 81 + value); 82 } 83 84 for (int i = 0; i < size; i++) { 85 Pair<Pattern, Integer> regex = mOptions.get(i); 86 if (regex.first.matcher(value).matches()) { 87 Log.d(TAG, "Found match at " + i + ": " + regex); 88 parentTemplate.setImageViewResource(childViewId, regex.second); 89 return; 90 } 91 } 92 Log.w(TAG, "No match for " + value); 93 } 94 95 /** 96 * Builder for {@link ImageTransformation} objects. 97 */ 98 public static class Builder { 99 private final AutofillId mId; 100 private final ArrayList<Pair<Pattern, Integer>> mOptions = new ArrayList<>(); 101 private boolean mDestroyed; 102 103 /** 104 * Create a new builder for a autofill id and add a first option. 105 * 106 * @param id id of the screen field that will be used to evaluate whether the image should 107 * be used. 108 * @param regex regular expression defining what should be matched to use this image. The 109 * pattern will be {@link Pattern#compile compiled} without setting any flags. 110 * @param resId resource id of the image (in the autofill service's package). The 111 * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id. 112 */ 113 public Builder(@NonNull AutofillId id, @NonNull String regex, @DrawableRes int resId) { 114 mId = Preconditions.checkNotNull(id); 115 116 addOption(regex, resId); 117 } 118 119 /** 120 * Adds an option to replace the child view with a different image when the regex matches. 121 * 122 * @param regex regular expression defining what should be matched to use this image. The 123 * pattern will be {@link Pattern#compile compiled} without setting any flags. 124 * @param resId resource id of the image (in the autofill service's package). The 125 * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id. 126 * 127 * @return this build 128 */ 129 public Builder addOption(@NonNull String regex, @DrawableRes int resId) { 130 throwIfDestroyed(); 131 132 Preconditions.checkArgument(resId != 0); 133 134 // Check regex 135 Pattern pattern = Pattern.compile(regex); 136 137 mOptions.add(new Pair<>(pattern, resId)); 138 return this; 139 } 140 141 /** 142 * Creates a new {@link ImageTransformation} instance. 143 * 144 * @throws IllegalStateException if no call to {@link #addOption(String, int)} was made. 145 */ 146 public ImageTransformation build() { 147 throwIfDestroyed(); 148 Preconditions.checkState(mOptions != null && !mOptions.isEmpty(), 149 "Must add at least one option"); 150 mDestroyed = true; 151 return new ImageTransformation(this); 152 } 153 154 private void throwIfDestroyed() { 155 Preconditions.checkState(!mDestroyed, "Already called build()"); 156 } 157 } 158 159 ///////////////////////////////////// 160 // Object "contract" methods. // 161 ///////////////////////////////////// 162 @Override 163 public String toString() { 164 if (!sDebug) return super.toString(); 165 166 return "ImageTransformation: [id=" + mId + ", options=" + mOptions + "]"; 167 } 168 169 ///////////////////////////////////// 170 // Parcelable "contract" methods. // 171 ///////////////////////////////////// 172 @Override 173 public int describeContents() { 174 return 0; 175 } 176 @Override 177 public void writeToParcel(Parcel parcel, int flags) { 178 parcel.writeParcelable(mId, flags); 179 180 final int size = mOptions.size(); 181 final String[] regexs = new String[size]; 182 final int[] resIds = new int[size]; 183 for (int i = 0; i < size; i++) { 184 Pair<Pattern, Integer> regex = mOptions.get(i); 185 regexs[i] = regex.first.pattern(); 186 resIds[i] = regex.second; 187 } 188 parcel.writeStringArray(regexs); 189 parcel.writeIntArray(resIds); 190 } 191 192 public static final Parcelable.Creator<ImageTransformation> CREATOR = 193 new Parcelable.Creator<ImageTransformation>() { 194 @Override 195 public ImageTransformation createFromParcel(Parcel parcel) { 196 final AutofillId id = parcel.readParcelable(null); 197 198 final String[] regexs = parcel.createStringArray(); 199 final int[] resIds = parcel.createIntArray(); 200 201 // Always go through the builder to ensure the data ingested by the system obeys the 202 // contract of the builder to avoid attacks using specially crafted parcels. 203 final ImageTransformation.Builder builder = new ImageTransformation.Builder(id, 204 regexs[0], resIds[0]); 205 206 final int size = regexs.length; 207 for (int i = 1; i < size; i++) { 208 builder.addOption(regexs[i], resIds[i]); 209 } 210 211 return builder.build(); 212 } 213 214 @Override 215 public ImageTransformation[] newArray(int size) { 216 return new ImageTransformation[size]; 217 } 218 }; 219} 220