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.util;
18 
19 import android.app.assist.AssistStructure;
20 import android.app.assist.AssistStructure.ViewNode;
21 import android.app.assist.AssistStructure.WindowNode;
22 import android.os.Bundle;
23 import android.service.autofill.FillContext;
24 import android.service.autofill.SaveInfo;
25 import android.support.annotation.NonNull;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewStructure.HtmlInfo;
29 import android.view.autofill.AutofillValue;
30 
31 import com.google.common.base.Joiner;
32 
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
36 import java.util.Set;
37 
38 public final class Util {
39 
40     public static final String EXTRA_DATASET_NAME = "dataset_name";
41     public static final String EXTRA_FOR_RESPONSE = "for_response";
42     public static final NodeFilter AUTOFILL_ID_FILTER = (node, id) ->
43             id.equals(node.getAutofillId());
44     private static final String TAG = "AutofillSample";
45     public static LogLevel sLoggingLevel = LogLevel.Off;
46 
bundleToString(StringBuilder builder, Bundle data)47     private static void bundleToString(StringBuilder builder, Bundle data) {
48         final Set<String> keySet = data.keySet();
49         builder.append("[Bundle with ").append(keySet.size()).append(" keys:");
50         for (String key : keySet) {
51             builder.append(' ').append(key).append('=');
52             Object value = data.get(key);
53             if ((value instanceof Bundle)) {
54                 bundleToString(builder, (Bundle) value);
55             } else {
56                 builder.append((value instanceof Object[])
57                         ? Arrays.toString((Object[]) value) : value);
58             }
59         }
60         builder.append(']');
61     }
62 
bundleToString(Bundle data)63     public static String bundleToString(Bundle data) {
64         if (data == null) {
65             return "N/A";
66         }
67         final StringBuilder builder = new StringBuilder();
68         bundleToString(builder, data);
69         return builder.toString();
70     }
71 
getTypeAsString(int type)72     public static String getTypeAsString(int type) {
73         switch (type) {
74             case View.AUTOFILL_TYPE_TEXT:
75                 return "TYPE_TEXT";
76             case View.AUTOFILL_TYPE_LIST:
77                 return "TYPE_LIST";
78             case View.AUTOFILL_TYPE_NONE:
79                 return "TYPE_NONE";
80             case View.AUTOFILL_TYPE_TOGGLE:
81                 return "TYPE_TOGGLE";
82             case View.AUTOFILL_TYPE_DATE:
83                 return "TYPE_DATE";
84         }
85         return "UNKNOWN_TYPE";
86     }
87 
getAutofillValueAndTypeAsString(AutofillValue value)88     private static String getAutofillValueAndTypeAsString(AutofillValue value) {
89         if (value == null) return "null";
90 
91         StringBuilder builder = new StringBuilder(value.toString()).append('(');
92         if (value.isText()) {
93             builder.append("isText");
94         } else if (value.isDate()) {
95             builder.append("isDate");
96         } else if (value.isToggle()) {
97             builder.append("isToggle");
98         } else if (value.isList()) {
99             builder.append("isList");
100         }
101         return builder.append(')').toString();
102     }
103 
dumpStructure(AssistStructure structure)104     public static void dumpStructure(AssistStructure structure) {
105         if (logVerboseEnabled()) {
106             int nodeCount = structure.getWindowNodeCount();
107             logv("dumpStructure(): component=%s numberNodes=%d",
108                     structure.getActivityComponent(), nodeCount);
109             for (int i = 0; i < nodeCount; i++) {
110                 logv("node #%d", i);
111                 WindowNode node = structure.getWindowNodeAt(i);
112                 dumpNode(new StringBuilder(), "  ", node.getRootViewNode(), 0);
113             }
114         }
115     }
116 
dumpNode(StringBuilder builder, String prefix, ViewNode node, int childNumber)117     private static void dumpNode(StringBuilder builder, String prefix, ViewNode node, int childNumber) {
118         builder.append(prefix)
119                 .append("child #").append(childNumber).append("\n");
120 
121         builder.append(prefix)
122                 .append("autoFillId: ").append(node.getAutofillId())
123                 .append("\tidEntry: ").append(node.getIdEntry())
124                 .append("\tid: ").append(node.getId())
125                 .append("\tclassName: ").append(node.getClassName())
126                 .append('\n');
127 
128         builder.append(prefix)
129                 .append("focused: ").append(node.isFocused())
130                 .append("\tvisibility").append(node.getVisibility())
131                 .append("\tchecked: ").append(node.isChecked())
132                 .append("\twebDomain: ").append(node.getWebDomain())
133                 .append("\thint: ").append(node.getHint())
134                 .append('\n');
135 
136         HtmlInfo htmlInfo = node.getHtmlInfo();
137 
138         if (htmlInfo != null) {
139             builder.append(prefix)
140                     .append("HTML TAG: ").append(htmlInfo.getTag())
141                     .append(" attrs: ").append(htmlInfo.getAttributes())
142                     .append('\n');
143         }
144 
145         String[] afHints = node.getAutofillHints();
146         CharSequence[] options = node.getAutofillOptions();
147         builder.append(prefix).append("afType: ").append(getTypeAsString(node.getAutofillType()))
148                 .append("\tafValue:")
149                 .append(getAutofillValueAndTypeAsString(node.getAutofillValue()))
150                 .append("\tafOptions:").append(options == null ? "N/A" : Arrays.toString(options))
151                 .append("\tafHints: ").append(afHints == null ? "N/A" : Arrays.toString(afHints))
152                 .append("\tinputType:").append(node.getInputType())
153                 .append('\n');
154 
155         int numberChildren = node.getChildCount();
156         builder.append(prefix).append("# children: ").append(numberChildren)
157                 .append("\ttext: ").append(node.getText())
158                 .append('\n');
159 
160         final String prefix2 = prefix + "  ";
161         for (int i = 0; i < numberChildren; i++) {
162             dumpNode(builder, prefix2, node.getChildAt(i), i);
163         }
164         logv(builder.toString());
165     }
166 
getSaveTypeAsString(int type)167     public static String getSaveTypeAsString(int type) {
168         List<String> types = new ArrayList<>();
169         if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) {
170             types.add("ADDRESS");
171         }
172         if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
173             types.add("CREDIT_CARD");
174         }
175         if ((type & SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS) != 0) {
176             types.add("EMAIL_ADDRESS");
177         }
178         if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) {
179             types.add("USERNAME");
180         }
181         if ((type & SaveInfo.SAVE_DATA_TYPE_PASSWORD) != 0) {
182             types.add("PASSWORD");
183         }
184         if (types.isEmpty()) {
185             return "UNKNOWN(" + type + ")";
186         }
187         return Joiner.on('|').join(types);
188     }
189 
190     /**
191      * Gets a node if it matches the filter criteria for the given id.
192      */
findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter filter)193     public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
194             @NonNull NodeFilter filter) {
195         for (FillContext context : contexts) {
196             ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
197             if (node != null) {
198                 return node;
199             }
200         }
201         return null;
202     }
203 
204     /**
205      * Gets a node if it matches the filter criteria for the given id.
206      */
findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter filter)207     public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
208             @NonNull NodeFilter filter) {
209         logv("Parsing request for activity %s", structure.getActivityComponent());
210         final int nodes = structure.getWindowNodeCount();
211         for (int i = 0; i < nodes; i++) {
212             final WindowNode windowNode = structure.getWindowNodeAt(i);
213             final ViewNode rootNode = windowNode.getRootViewNode();
214             final ViewNode node = findNodeByFilter(rootNode, id, filter);
215             if (node != null) {
216                 return node;
217             }
218         }
219         return null;
220     }
221 
222     /**
223      * Gets a node if it matches the filter criteria for the given id.
224      */
findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter filter)225     public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
226             @NonNull NodeFilter filter) {
227         if (filter.matches(node, id)) {
228             return node;
229         }
230         final int childrenSize = node.getChildCount();
231         if (childrenSize > 0) {
232             for (int i = 0; i < childrenSize; i++) {
233                 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
234                 if (found != null) {
235                     return found;
236                 }
237             }
238         }
239         return null;
240     }
241 
logd(String message, Object... params)242     public static void logd(String message, Object... params) {
243         if (logDebugEnabled()) {
244             Log.d(TAG, String.format(message, params));
245         }
246     }
247 
logv(String message, Object... params)248     public static void logv(String message, Object... params) {
249         if (logVerboseEnabled()) {
250             Log.v(TAG, String.format(message, params));
251         }
252     }
253 
logDebugEnabled()254     public static boolean logDebugEnabled() {
255         return sLoggingLevel.ordinal() >= LogLevel.Debug.ordinal();
256     }
257 
logVerboseEnabled()258     public static boolean logVerboseEnabled() {
259         return sLoggingLevel.ordinal() >= LogLevel.Verbose.ordinal();
260     }
261 
logw(String message, Object... params)262     public static void logw(String message, Object... params) {
263         Log.w(TAG, String.format(message, params));
264     }
265 
logw(Throwable throwable, String message, Object... params)266     public static void logw(Throwable throwable, String message, Object... params) {
267         Log.w(TAG, String.format(message, params), throwable);
268     }
269 
loge(String message, Object... params)270     public static void loge(String message, Object... params) {
271         Log.e(TAG, String.format(message, params));
272     }
273 
loge(Throwable throwable, String message, Object... params)274     public static void loge(Throwable throwable, String message, Object... params) {
275         Log.e(TAG, String.format(message, params), throwable);
276     }
277 
setLoggingLevel(LogLevel level)278     public static void setLoggingLevel(LogLevel level) {
279         sLoggingLevel = level;
280     }
281 
282     /**
283      * Helper method for getting the index of a CharSequence object in an array.
284      */
indexOf(@onNull CharSequence[] array, CharSequence charSequence)285     public static int indexOf(@NonNull CharSequence[] array, CharSequence charSequence) {
286         int index = -1;
287         if (charSequence == null) {
288             return index;
289         }
290         for (int i = 0; i < array.length; i++) {
291             if (charSequence.equals(array[i])) {
292                 index = i;
293                 break;
294             }
295         }
296         return index;
297     }
298 
299     public enum LogLevel {Off, Debug, Verbose}
300 
301     public enum DalCheckRequirement {Disabled, LoginOnly, AllUrls}
302 
303     /**
304      * Helper interface used to filter Assist nodes.
305      */
306     public interface NodeFilter {
307         /**
308          * Returns whether the node passes the filter for such given id.
309          */
matches(ViewNode node, Object id)310         boolean matches(ViewNode node, Object id);
311     }
312 }