1 /* 2 * Copyright (C) 2020 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.inlinefillservice; 18 19 import static com.example.android.inlinefillservice.InlineFillService.TAG; 20 21 import android.app.assist.AssistStructure; 22 import android.content.Context; 23 import android.service.autofill.FillContext; 24 import android.service.autofill.FillRequest; 25 import android.util.ArrayMap; 26 import android.util.Log; 27 import android.view.View; 28 import android.view.autofill.AutofillId; 29 import android.widget.Toast; 30 31 import androidx.annotation.NonNull; 32 33 import java.util.List; 34 import java.util.Map; 35 36 final class Helper { 37 38 /** 39 * Displays a toast with the given message. 40 */ showMessage(@onNull Context context, @NonNull CharSequence message)41 static void showMessage(@NonNull Context context, @NonNull CharSequence message) { 42 Log.i(TAG, message.toString()); 43 Toast.makeText(context, message, Toast.LENGTH_LONG).show(); 44 } 45 46 /** 47 * Extracts the autofillable fields from the request through assist structure. 48 */ getAutofillableFields(@onNull FillRequest request)49 static ArrayMap<String, AutofillId> getAutofillableFields(@NonNull FillRequest request) { 50 AssistStructure structure = getLatestAssistStructure(request); 51 return getAutofillableFields(structure); 52 } 53 54 /** 55 * Helper method to get the {@link AssistStructure} associated with the latest request 56 * in an autofill context. 57 */ 58 @NonNull getLatestAssistStructure(@onNull FillRequest request)59 private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) { 60 List<FillContext> fillContexts = request.getFillContexts(); 61 return fillContexts.get(fillContexts.size() - 1).getStructure(); 62 } 63 64 /** 65 * Parses the {@link AssistStructure} representing the activity being autofilled, and returns a 66 * map of autofillable fields (represented by their autofill ids) mapped by the hint associate 67 * with them. 68 * 69 * <p>An autofillable field is a {@link AssistStructure.ViewNode} whose getHint(ViewNode) 70 * method. 71 */ 72 @NonNull getAutofillableFields( @onNull AssistStructure structure)73 private static ArrayMap<String, AutofillId> getAutofillableFields( 74 @NonNull AssistStructure structure) { 75 ArrayMap<String, AutofillId> fields = new ArrayMap<>(); 76 int nodes = structure.getWindowNodeCount(); 77 for (int i = 0; i < nodes; i++) { 78 AssistStructure.ViewNode node = structure.getWindowNodeAt(i).getRootViewNode(); 79 addAutofillableFields(fields, node); 80 } 81 ArrayMap<String, AutofillId> result = new ArrayMap<>(); 82 int filedCount = fields.size(); 83 for (int i = 0; i < filedCount; i++) { 84 String key = fields.keyAt(i); 85 AutofillId value = fields.valueAt(i); 86 // For fields with no hint we just use Field 87 if (key.equals(value.toString())) { 88 result.put("Field:" + i + "-", fields.valueAt(i)); 89 } else { 90 result.put(key, fields.valueAt(i)); 91 } 92 } 93 return result; 94 } 95 96 /** 97 * Adds any autofillable view from the {@link AssistStructure.ViewNode} and its descendants to 98 * the map. 99 */ addAutofillableFields(@onNull Map<String, AutofillId> fields, @NonNull AssistStructure.ViewNode node)100 private static void addAutofillableFields(@NonNull Map<String, AutofillId> fields, 101 @NonNull AssistStructure.ViewNode node) { 102 if (node.getAutofillType() == View.AUTOFILL_TYPE_TEXT) { 103 if (!fields.containsValue(node.getAutofillId())) { 104 final String key; 105 if (node.getHint() != null) { 106 key = node.getHint().toLowerCase(); 107 } else if (node.getAutofillHints() != null) { 108 key = node.getAutofillHints()[0].toLowerCase(); 109 } else { 110 key = node.getAutofillId().toString(); 111 } 112 fields.put(key, node.getAutofillId()); 113 } 114 } 115 int childrenSize = node.getChildCount(); 116 for (int i = 0; i < childrenSize; i++) { 117 addAutofillableFields(fields, node.getChildAt(i)); 118 } 119 } 120 } 121