| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.autofill.service.util; |
| |
| import android.app.assist.AssistStructure; |
| import android.app.assist.AssistStructure.ViewNode; |
| import android.app.assist.AssistStructure.WindowNode; |
| import android.os.Bundle; |
| import android.service.autofill.FillContext; |
| import android.service.autofill.SaveInfo; |
| import android.support.annotation.NonNull; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewStructure.HtmlInfo; |
| import android.view.autofill.AutofillValue; |
| |
| import com.google.common.base.Joiner; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Set; |
| |
| public final class Util { |
| |
| public static final String EXTRA_DATASET_NAME = "dataset_name"; |
| public static final String EXTRA_FOR_RESPONSE = "for_response"; |
| public static final NodeFilter AUTOFILL_ID_FILTER = (node, id) -> |
| id.equals(node.getAutofillId()); |
| private static final String TAG = "AutofillSample"; |
| public static LogLevel sLoggingLevel = LogLevel.Off; |
| |
| private static void bundleToString(StringBuilder builder, Bundle data) { |
| final Set<String> keySet = data.keySet(); |
| builder.append("[Bundle with ").append(keySet.size()).append(" keys:"); |
| for (String key : keySet) { |
| builder.append(' ').append(key).append('='); |
| Object value = data.get(key); |
| if ((value instanceof Bundle)) { |
| bundleToString(builder, (Bundle) value); |
| } else { |
| builder.append((value instanceof Object[]) |
| ? Arrays.toString((Object[]) value) : value); |
| } |
| } |
| builder.append(']'); |
| } |
| |
| public static String bundleToString(Bundle data) { |
| if (data == null) { |
| return "N/A"; |
| } |
| final StringBuilder builder = new StringBuilder(); |
| bundleToString(builder, data); |
| return builder.toString(); |
| } |
| |
| public static String getTypeAsString(int type) { |
| switch (type) { |
| case View.AUTOFILL_TYPE_TEXT: |
| return "TYPE_TEXT"; |
| case View.AUTOFILL_TYPE_LIST: |
| return "TYPE_LIST"; |
| case View.AUTOFILL_TYPE_NONE: |
| return "TYPE_NONE"; |
| case View.AUTOFILL_TYPE_TOGGLE: |
| return "TYPE_TOGGLE"; |
| case View.AUTOFILL_TYPE_DATE: |
| return "TYPE_DATE"; |
| } |
| return "UNKNOWN_TYPE"; |
| } |
| |
| private static String getAutofillValueAndTypeAsString(AutofillValue value) { |
| if (value == null) return "null"; |
| |
| StringBuilder builder = new StringBuilder(value.toString()).append('('); |
| if (value.isText()) { |
| builder.append("isText"); |
| } else if (value.isDate()) { |
| builder.append("isDate"); |
| } else if (value.isToggle()) { |
| builder.append("isToggle"); |
| } else if (value.isList()) { |
| builder.append("isList"); |
| } |
| return builder.append(')').toString(); |
| } |
| |
| public static void dumpStructure(AssistStructure structure) { |
| if (logVerboseEnabled()) { |
| int nodeCount = structure.getWindowNodeCount(); |
| logv("dumpStructure(): component=%s numberNodes=%d", |
| structure.getActivityComponent(), nodeCount); |
| for (int i = 0; i < nodeCount; i++) { |
| logv("node #%d", i); |
| WindowNode node = structure.getWindowNodeAt(i); |
| dumpNode(new StringBuilder(), " ", node.getRootViewNode(), 0); |
| } |
| } |
| } |
| |
| private static void dumpNode(StringBuilder builder, String prefix, ViewNode node, int childNumber) { |
| builder.append(prefix) |
| .append("child #").append(childNumber).append("\n"); |
| |
| builder.append(prefix) |
| .append("autoFillId: ").append(node.getAutofillId()) |
| .append("\tidEntry: ").append(node.getIdEntry()) |
| .append("\tid: ").append(node.getId()) |
| .append("\tclassName: ").append(node.getClassName()) |
| .append('\n'); |
| |
| builder.append(prefix) |
| .append("focused: ").append(node.isFocused()) |
| .append("\tvisibility").append(node.getVisibility()) |
| .append("\tchecked: ").append(node.isChecked()) |
| .append("\twebDomain: ").append(node.getWebDomain()) |
| .append("\thint: ").append(node.getHint()) |
| .append('\n'); |
| |
| HtmlInfo htmlInfo = node.getHtmlInfo(); |
| |
| if (htmlInfo != null) { |
| builder.append(prefix) |
| .append("HTML TAG: ").append(htmlInfo.getTag()) |
| .append(" attrs: ").append(htmlInfo.getAttributes()) |
| .append('\n'); |
| } |
| |
| String[] afHints = node.getAutofillHints(); |
| CharSequence[] options = node.getAutofillOptions(); |
| builder.append(prefix).append("afType: ").append(getTypeAsString(node.getAutofillType())) |
| .append("\tafValue:") |
| .append(getAutofillValueAndTypeAsString(node.getAutofillValue())) |
| .append("\tafOptions:").append(options == null ? "N/A" : Arrays.toString(options)) |
| .append("\tafHints: ").append(afHints == null ? "N/A" : Arrays.toString(afHints)) |
| .append("\tinputType:").append(node.getInputType()) |
| .append('\n'); |
| |
| int numberChildren = node.getChildCount(); |
| builder.append(prefix).append("# children: ").append(numberChildren) |
| .append("\ttext: ").append(node.getText()) |
| .append('\n'); |
| |
| final String prefix2 = prefix + " "; |
| for (int i = 0; i < numberChildren; i++) { |
| dumpNode(builder, prefix2, node.getChildAt(i), i); |
| } |
| logv(builder.toString()); |
| } |
| |
| public static String getSaveTypeAsString(int type) { |
| List<String> types = new ArrayList<>(); |
| if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) { |
| types.add("ADDRESS"); |
| } |
| if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) { |
| types.add("CREDIT_CARD"); |
| } |
| if ((type & SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS) != 0) { |
| types.add("EMAIL_ADDRESS"); |
| } |
| if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) { |
| types.add("USERNAME"); |
| } |
| if ((type & SaveInfo.SAVE_DATA_TYPE_PASSWORD) != 0) { |
| types.add("PASSWORD"); |
| } |
| if (types.isEmpty()) { |
| return "UNKNOWN(" + type + ")"; |
| } |
| return Joiner.on('|').join(types); |
| } |
| |
| /** |
| * Gets a node if it matches the filter criteria for the given id. |
| */ |
| public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id, |
| @NonNull NodeFilter filter) { |
| for (FillContext context : contexts) { |
| ViewNode node = findNodeByFilter(context.getStructure(), id, filter); |
| if (node != null) { |
| return node; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets a node if it matches the filter criteria for the given id. |
| */ |
| public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id, |
| @NonNull NodeFilter filter) { |
| logv("Parsing request for activity %s", structure.getActivityComponent()); |
| final int nodes = structure.getWindowNodeCount(); |
| for (int i = 0; i < nodes; i++) { |
| final WindowNode windowNode = structure.getWindowNodeAt(i); |
| final ViewNode rootNode = windowNode.getRootViewNode(); |
| final ViewNode node = findNodeByFilter(rootNode, id, filter); |
| if (node != null) { |
| return node; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets a node if it matches the filter criteria for the given id. |
| */ |
| public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id, |
| @NonNull NodeFilter filter) { |
| if (filter.matches(node, id)) { |
| return node; |
| } |
| final int childrenSize = node.getChildCount(); |
| if (childrenSize > 0) { |
| for (int i = 0; i < childrenSize; i++) { |
| final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter); |
| if (found != null) { |
| return found; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static void logd(String message, Object... params) { |
| if (logDebugEnabled()) { |
| Log.d(TAG, String.format(message, params)); |
| } |
| } |
| |
| public static void logv(String message, Object... params) { |
| if (logVerboseEnabled()) { |
| Log.v(TAG, String.format(message, params)); |
| } |
| } |
| |
| public static boolean logDebugEnabled() { |
| return sLoggingLevel.ordinal() >= LogLevel.Debug.ordinal(); |
| } |
| |
| public static boolean logVerboseEnabled() { |
| return sLoggingLevel.ordinal() >= LogLevel.Verbose.ordinal(); |
| } |
| |
| public static void logw(String message, Object... params) { |
| Log.w(TAG, String.format(message, params)); |
| } |
| |
| public static void logw(Throwable throwable, String message, Object... params) { |
| Log.w(TAG, String.format(message, params), throwable); |
| } |
| |
| public static void loge(String message, Object... params) { |
| Log.e(TAG, String.format(message, params)); |
| } |
| |
| public static void loge(Throwable throwable, String message, Object... params) { |
| Log.e(TAG, String.format(message, params), throwable); |
| } |
| |
| public static void setLoggingLevel(LogLevel level) { |
| sLoggingLevel = level; |
| } |
| |
| /** |
| * Helper method for getting the index of a CharSequence object in an array. |
| */ |
| public static int indexOf(@NonNull CharSequence[] array, CharSequence charSequence) { |
| int index = -1; |
| if (charSequence == null) { |
| return index; |
| } |
| for (int i = 0; i < array.length; i++) { |
| if (charSequence.equals(array[i])) { |
| index = i; |
| break; |
| } |
| } |
| return index; |
| } |
| |
| public enum LogLevel {Off, Debug, Verbose} |
| |
| public enum DalCheckRequirement {Disabled, LoginOnly, AllUrls} |
| |
| /** |
| * Helper interface used to filter Assist nodes. |
| */ |
| public interface NodeFilter { |
| /** |
| * Returns whether the node passes the filter for such given id. |
| */ |
| boolean matches(ViewNode node, Object id); |
| } |
| } |