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 17 package com.example.android.autofill.service.data.adapter; 18 19 import android.app.assist.AssistStructure; 20 import android.content.IntentSender; 21 import android.service.autofill.Dataset; 22 import android.util.MutableBoolean; 23 import android.view.View; 24 import android.view.autofill.AutofillId; 25 import android.view.autofill.AutofillValue; 26 import android.widget.RemoteViews; 27 28 import com.example.android.autofill.service.AutofillHints; 29 import com.example.android.autofill.service.ClientParser; 30 import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; 31 import com.example.android.autofill.service.model.FieldType; 32 import com.example.android.autofill.service.model.FieldTypeWithHeuristics; 33 import com.example.android.autofill.service.model.FilledAutofillField; 34 35 import java.util.Arrays; 36 import java.util.HashMap; 37 import java.util.Map; 38 import java.util.function.Function; 39 40 import static com.example.android.autofill.service.util.Util.indexOf; 41 import static com.example.android.autofill.service.util.Util.logv; 42 import static com.example.android.autofill.service.util.Util.logw; 43 import static java.util.stream.Collectors.toMap; 44 45 public class DatasetAdapter { 46 private final ClientParser mClientParser; 47 48 public DatasetAdapter(ClientParser clientParser) { 49 mClientParser = clientParser; 50 } 51 52 /** 53 * Wraps autofill data in a {@link Dataset} object which can then be sent back to the client. 54 */ 55 public Dataset buildDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint, 56 DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, 57 RemoteViews remoteViews) { 58 return buildDataset(fieldTypesByAutofillHint, datasetWithFilledAutofillFields, remoteViews, 59 null); 60 } 61 62 public Dataset buildDatasetForFocusedNode(FilledAutofillField filledAutofillField, 63 FieldType fieldType, RemoteViews remoteViews) { 64 Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews); 65 boolean setAtLeastOneValue = bindDatasetToFocusedNode(filledAutofillField, 66 fieldType, datasetBuilder); 67 if (!setAtLeastOneValue) { 68 return null; 69 } 70 return datasetBuilder.build(); 71 } 72 73 /** 74 * Wraps autofill data in a {@link Dataset} object with an IntentSender, which can then be 75 * sent back to the client. 76 */ 77 public Dataset buildDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint, 78 DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, 79 RemoteViews remoteViews, IntentSender intentSender) { 80 Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews); 81 if (intentSender != null) { 82 datasetBuilder.setAuthentication(intentSender); 83 } 84 boolean setAtLeastOneValue = bindDataset(fieldTypesByAutofillHint, 85 datasetWithFilledAutofillFields, datasetBuilder); 86 if (!setAtLeastOneValue) { 87 return null; 88 } 89 return datasetBuilder.build(); 90 } 91 92 /** 93 * Build an autofill {@link Dataset} using saved data and the client's AssistStructure. 94 */ 95 private boolean bindDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint, 96 DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, 97 Dataset.Builder datasetBuilder) { 98 MutableBoolean setValueAtLeastOnce = new MutableBoolean(false); 99 Map<String, FilledAutofillField> filledAutofillFieldsByTypeName = 100 datasetWithFilledAutofillFields.filledAutofillFields.stream() 101 .collect(toMap(FilledAutofillField::getFieldTypeName, Function.identity())); 102 mClientParser.parse((node) -> 103 parseAutofillFields(node, fieldTypesByAutofillHint, filledAutofillFieldsByTypeName, 104 datasetBuilder, setValueAtLeastOnce) 105 ); 106 return setValueAtLeastOnce.value; 107 } 108 109 private boolean bindDatasetToFocusedNode(FilledAutofillField field, 110 FieldType fieldType, Dataset.Builder builder) { 111 MutableBoolean setValueAtLeastOnce = new MutableBoolean(false); 112 mClientParser.parse((node) -> { 113 if (node.isFocused() && node.getAutofillId() != null) { 114 bindValueToNode(node, field, builder, setValueAtLeastOnce); 115 } 116 }); 117 return setValueAtLeastOnce.value; 118 } 119 120 private void parseAutofillFields(AssistStructure.ViewNode viewNode, 121 HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint, 122 Map<String, FilledAutofillField> filledAutofillFieldsByTypeName, 123 Dataset.Builder builder, MutableBoolean setValueAtLeastOnce) { 124 String[] rawHints = viewNode.getAutofillHints(); 125 if (rawHints == null || rawHints.length == 0) { 126 logv("No af hints at ViewNode - %s", viewNode.getIdEntry()); 127 return; 128 } 129 String fieldTypeName = AutofillHints.getFieldTypeNameFromAutofillHints( 130 fieldTypesByAutofillHint, Arrays.asList(rawHints)); 131 if (fieldTypeName == null) { 132 return; 133 } 134 FilledAutofillField field = filledAutofillFieldsByTypeName.get(fieldTypeName); 135 if (field == null) { 136 return; 137 } 138 bindValueToNode(viewNode, field, builder, setValueAtLeastOnce); 139 } 140 141 void bindValueToNode(AssistStructure.ViewNode viewNode, 142 FilledAutofillField field, Dataset.Builder builder, 143 MutableBoolean setValueAtLeastOnce) { 144 AutofillId autofillId = viewNode.getAutofillId(); 145 if (autofillId == null) { 146 logw("Autofill ID null for %s", viewNode.toString()); 147 return; 148 } 149 int autofillType = viewNode.getAutofillType(); 150 switch (autofillType) { 151 case View.AUTOFILL_TYPE_LIST: 152 CharSequence[] options = viewNode.getAutofillOptions(); 153 int listValue = -1; 154 if (options != null) { 155 listValue = indexOf(viewNode.getAutofillOptions(), field.getTextValue()); 156 } 157 if (listValue != -1) { 158 builder.setValue(autofillId, AutofillValue.forList(listValue)); 159 setValueAtLeastOnce.value = true; 160 } 161 break; 162 case View.AUTOFILL_TYPE_DATE: 163 Long dateValue = field.getDateValue(); 164 if (dateValue != null) { 165 builder.setValue(autofillId, AutofillValue.forDate(dateValue)); 166 setValueAtLeastOnce.value = true; 167 } 168 break; 169 case View.AUTOFILL_TYPE_TEXT: 170 String textValue = field.getTextValue(); 171 if (textValue != null) { 172 builder.setValue(autofillId, AutofillValue.forText(textValue)); 173 setValueAtLeastOnce.value = true; 174 } 175 break; 176 case View.AUTOFILL_TYPE_TOGGLE: 177 Boolean toggleValue = field.getToggleValue(); 178 if (toggleValue != null) { 179 builder.setValue(autofillId, AutofillValue.forToggle(toggleValue)); 180 setValueAtLeastOnce.value = true; 181 } 182 break; 183 case View.AUTOFILL_TYPE_NONE: 184 default: 185 logw("Invalid autofill type - %d", autofillType); 186 break; 187 } 188 } 189 } 190