Home | History | Annotate | Download | only in util
      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 
     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 
     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 
     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 
     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 
    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 
    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 
    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      */
    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      */
    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      */
    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 
    242     public static void logd(String message, Object... params) {
    243         if (logDebugEnabled()) {
    244             Log.d(TAG, String.format(message, params));
    245         }
    246     }
    247 
    248     public static void logv(String message, Object... params) {
    249         if (logVerboseEnabled()) {
    250             Log.v(TAG, String.format(message, params));
    251         }
    252     }
    253 
    254     public static boolean logDebugEnabled() {
    255         return sLoggingLevel.ordinal() >= LogLevel.Debug.ordinal();
    256     }
    257 
    258     public static boolean logVerboseEnabled() {
    259         return sLoggingLevel.ordinal() >= LogLevel.Verbose.ordinal();
    260     }
    261 
    262     public static void logw(String message, Object... params) {
    263         Log.w(TAG, String.format(message, params));
    264     }
    265 
    266     public static void logw(Throwable throwable, String message, Object... params) {
    267         Log.w(TAG, String.format(message, params), throwable);
    268     }
    269 
    270     public static void loge(String message, Object... params) {
    271         Log.e(TAG, String.format(message, params));
    272     }
    273 
    274     public static void loge(Throwable throwable, String message, Object... params) {
    275         Log.e(TAG, String.format(message, params), throwable);
    276     }
    277 
    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      */
    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          */
    310         boolean matches(ViewNode node, Object id);
    311     }
    312 }