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