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 package com.example.android.autofill.service.model; 17 18 import android.service.autofill.Dataset; 19 import android.support.annotation.NonNull; 20 import android.util.Log; 21 import android.view.View; 22 import android.view.autofill.AutofillId; 23 import android.view.autofill.AutofillValue; 24 25 import com.example.android.autofill.service.AutofillFieldMetadata; 26 import com.example.android.autofill.service.AutofillFieldMetadataCollection; 27 import com.example.android.autofill.service.AutofillHints; 28 import com.example.android.autofill.service.W3cHints; 29 import com.google.gson.annotations.Expose; 30 31 import java.util.HashMap; 32 import java.util.List; 33 34 import static com.example.android.autofill.service.CommonUtil.DEBUG; 35 import static com.example.android.autofill.service.CommonUtil.TAG; 36 37 /** 38 * FilledAutofillFieldCollection is the model that holds all of the data on a client app's page, 39 * plus the dataset name associated with it. 40 */ 41 public final class FilledAutofillFieldCollection { 42 @Expose 43 private final HashMap<String, FilledAutofillField> mHintMap; 44 @Expose 45 private String mDatasetName; 46 47 public FilledAutofillFieldCollection() { 48 this(null, new HashMap<String, FilledAutofillField>()); 49 } 50 51 public FilledAutofillFieldCollection(String datasetName, HashMap<String, FilledAutofillField> hintMap) { 52 mHintMap = hintMap; 53 mDatasetName = datasetName; 54 } 55 56 private static boolean isW3cSectionPrefix(String hint) { 57 return hint.startsWith(W3cHints.PREFIX_SECTION); 58 } 59 60 private static boolean isW3cAddressType(String hint) { 61 switch (hint) { 62 case W3cHints.SHIPPING: 63 case W3cHints.BILLING: 64 return true; 65 } 66 return false; 67 } 68 69 private static boolean isW3cTypePrefix(String hint) { 70 switch (hint) { 71 case W3cHints.PREFIX_WORK: 72 case W3cHints.PREFIX_FAX: 73 case W3cHints.PREFIX_HOME: 74 case W3cHints.PREFIX_PAGER: 75 return true; 76 } 77 return false; 78 } 79 80 private static boolean isW3cTypeHint(String hint) { 81 switch (hint) { 82 case W3cHints.TEL: 83 case W3cHints.TEL_COUNTRY_CODE: 84 case W3cHints.TEL_NATIONAL: 85 case W3cHints.TEL_AREA_CODE: 86 case W3cHints.TEL_LOCAL: 87 case W3cHints.TEL_LOCAL_PREFIX: 88 case W3cHints.TEL_LOCAL_SUFFIX: 89 case W3cHints.TEL_EXTENSION: 90 case W3cHints.EMAIL: 91 case W3cHints.IMPP: 92 return true; 93 } 94 Log.w(TAG, "Invalid W3C type hint: " + hint); 95 return false; 96 } 97 98 /** 99 * Returns the name of the {@link Dataset}. 100 */ 101 public String getDatasetName() { 102 return mDatasetName; 103 } 104 105 /** 106 * Sets the {@link Dataset} name. 107 */ 108 public void setDatasetName(String datasetName) { 109 mDatasetName = datasetName; 110 } 111 112 /** 113 * Adds a {@code FilledAutofillField} to the collection, indexed by all of its hints. 114 */ 115 public void add(@NonNull FilledAutofillField filledAutofillField) { 116 String[] autofillHints = filledAutofillField.getAutofillHints(); 117 String nextHint = null; 118 for (int i = 0; i < autofillHints.length; i++) { 119 String hint = autofillHints[i]; 120 if (i < autofillHints.length - 1) { 121 nextHint = autofillHints[i + 1]; 122 } 123 // First convert the compound W3C autofill hints 124 if (isW3cSectionPrefix(hint) && i < autofillHints.length - 1) { 125 hint = autofillHints[++i]; 126 if (DEBUG) Log.d(TAG, "Hint is a W3C section prefix; using " + hint + " instead"); 127 if (i < autofillHints.length - 1) { 128 nextHint = autofillHints[i + 1]; 129 } 130 } 131 if (isW3cTypePrefix(hint) && nextHint != null && isW3cTypeHint(nextHint)) { 132 hint = nextHint; 133 i++; 134 if (DEBUG) Log.d(TAG, "Hint is a W3C type prefix; using " + hint + " instead"); 135 } 136 if (isW3cAddressType(hint) && nextHint != null) { 137 hint = nextHint; 138 i++; 139 if (DEBUG) Log.d(TAG, "Hint is a W3C address prefix; using " + hint + " instead"); 140 } 141 142 // Then check if the "actual" hint is supported. 143 144 145 if (AutofillHints.isValidHint(hint)) { 146 mHintMap.put(hint, filledAutofillField); 147 } else { 148 Log.e(TAG, "Invalid hint: " + autofillHints[i]); 149 } 150 } 151 } 152 153 /** 154 * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId} 155 * in a {@code AutofillFieldMetadataCollection}. 156 * 157 * In other words, it constructs an autofill 158 * {@link Dataset.Builder} by applying saved values (from this {@code FilledAutofillFieldCollection}) 159 * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current 160 * page the user is on. 161 */ 162 public boolean applyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, 163 Dataset.Builder datasetBuilder) { 164 boolean setValueAtLeastOnce = false; 165 List<String> allHints = autofillFieldMetadataCollection.getAllHints(); 166 for (int hintIndex = 0; hintIndex < allHints.size(); hintIndex++) { 167 String hint = allHints.get(hintIndex); 168 List<AutofillFieldMetadata> fillableAutofillFields = 169 autofillFieldMetadataCollection.getFieldsForHint(hint); 170 if (fillableAutofillFields == null) { 171 continue; 172 } 173 for (int autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.size(); autofillFieldIndex++) { 174 FilledAutofillField filledAutofillField = mHintMap.get(hint); 175 if (filledAutofillField == null) { 176 continue; 177 } 178 AutofillFieldMetadata autofillFieldMetadata = fillableAutofillFields.get(autofillFieldIndex); 179 AutofillId autofillId = autofillFieldMetadata.getId(); 180 int autofillType = autofillFieldMetadata.getAutofillType(); 181 switch (autofillType) { 182 case View.AUTOFILL_TYPE_LIST: 183 int listValue = autofillFieldMetadata.getAutofillOptionIndex(filledAutofillField.getTextValue()); 184 if (listValue != -1) { 185 datasetBuilder.setValue(autofillId, AutofillValue.forList(listValue)); 186 setValueAtLeastOnce = true; 187 } 188 break; 189 case View.AUTOFILL_TYPE_DATE: 190 Long dateValue = filledAutofillField.getDateValue(); 191 if (dateValue != null) { 192 datasetBuilder.setValue(autofillId, AutofillValue.forDate(dateValue)); 193 setValueAtLeastOnce = true; 194 } 195 break; 196 case View.AUTOFILL_TYPE_TEXT: 197 String textValue = filledAutofillField.getTextValue(); 198 if (textValue != null) { 199 datasetBuilder.setValue(autofillId, AutofillValue.forText(textValue)); 200 setValueAtLeastOnce = true; 201 } 202 break; 203 case View.AUTOFILL_TYPE_TOGGLE: 204 Boolean toggleValue = filledAutofillField.getToggleValue(); 205 if (toggleValue != null) { 206 datasetBuilder.setValue(autofillId, AutofillValue.forToggle(toggleValue)); 207 setValueAtLeastOnce = true; 208 } 209 break; 210 case View.AUTOFILL_TYPE_NONE: 211 default: 212 Log.w(TAG, "Invalid autofill type - " + autofillType); 213 break; 214 } 215 } 216 } 217 return setValueAtLeastOnce; 218 } 219 220 /** 221 * Takes in a list of autofill hints (autofillHints), usually associated with a View or set of 222 * Views. Returns whether any of the filled fields on the page have at least 1 of these 223 * autofillHints. 224 */ 225 public boolean helpsWithHints(List<String> autofillHints) { 226 for (int i = 0; i < autofillHints.size(); i++) { 227 String autofillHint = autofillHints.get(i); 228 if (mHintMap.containsKey(autofillHint) && !mHintMap.get(autofillHint).isNull()) { 229 return true; 230 } 231 } 232 return false; 233 } 234 } 235