ImageTransformation.java revision a5083c40d513184bc84ac39def7303a1424fa4c8
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, Pattern.compile("^4815.*$"), 46 * R.drawable.ic_credit_card_logo1) 47 * .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2) 48 * .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3) 49 * .build(); 50 * </pre> 51 * 52 * <p>There is no imposed limit in the number of options, but keep in mind that regexs are 53 * expensive to evaluate, so use the minimum number of regexs and add the most common first 54 * (for example, if this is a tranformation for a credit card logo and the most common credit card 55 * issuers are banks X and Y, add the regexes that resolves these 2 banks first). 56 */ 57public final class ImageTransformation extends InternalTransformation implements Transformation, 58 Parcelable { 59 private static final String TAG = "ImageTransformation"; 60 61 private final AutofillId mId; 62 private final ArrayList<Pair<Pattern, Integer>> mOptions; 63 64 private ImageTransformation(Builder builder) { 65 mId = builder.mId; 66 mOptions = builder.mOptions; 67 } 68 69 /** @hide */ 70 @TestApi 71 @Override 72 public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate, 73 int childViewId) throws Exception { 74 final String value = finder.findByAutofillId(mId); 75 if (value == null) { 76 Log.w(TAG, "No view for id " + mId); 77 return; 78 } 79 final int size = mOptions.size(); 80 if (sDebug) { 81 Log.d(TAG, size + " multiple options on id " + childViewId + " to compare against"); 82 } 83 84 for (int i = 0; i < size; i++) { 85 final Pair<Pattern, Integer> option = mOptions.get(i); 86 try { 87 if (option.first.matcher(value).matches()) { 88 Log.d(TAG, "Found match at " + i + ": " + option); 89 parentTemplate.setImageViewResource(childViewId, option.second); 90 return; 91 } 92 } catch (Exception e) { 93 // Do not log full exception to avoid PII leaking 94 Log.w(TAG, "Error matching regex #" + i + "(" + option.first.pattern() + ") on id " 95 + option.second + ": " + e.getClass()); 96 throw e; 97 98 } 99 } 100 if (sDebug) Log.d(TAG, "No match for " + value); 101 } 102 103 /** 104 * Builder for {@link ImageTransformation} objects. 105 */ 106 public static class Builder { 107 private final AutofillId mId; 108 private final ArrayList<Pair<Pattern, Integer>> mOptions = new ArrayList<>(); 109 private boolean mDestroyed; 110 111 /** 112 * Create a new builder for a autofill id and add a first option. 113 * 114 * @param id id of the screen field that will be used to evaluate whether the image should 115 * be used. 116 * @param regex regular expression defining what should be matched to use this image. 117 * @param resId resource id of the image (in the autofill service's package). The 118 * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id. 119 */ 120 public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId) { 121 mId = Preconditions.checkNotNull(id); 122 123 addOption(regex, resId); 124 } 125 126 /** 127 * Adds an option to replace the child view with a different image when the regex matches. 128 * 129 * @param regex regular expression defining what should be matched to use this image. 130 * @param resId resource id of the image (in the autofill service's package). The 131 * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id. 132 * 133 * @return this build 134 */ 135 public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId) { 136 throwIfDestroyed(); 137 138 Preconditions.checkNotNull(regex); 139 Preconditions.checkArgument(resId != 0); 140 141 mOptions.add(new Pair<>(regex, resId)); 142 return this; 143 } 144 145 /** 146 * Creates a new {@link ImageTransformation} instance. 147 */ 148 public ImageTransformation build() { 149 throwIfDestroyed(); 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 Pattern[] regexs = new Pattern[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; 186 resIds[i] = regex.second; 187 } 188 parcel.writeSerializable(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 Pattern[] regexs = (Pattern[]) parcel.readSerializable(); 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