Home | History | Annotate | Download | only in adapter
      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