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 }