// Retrieve view type
private void readLayout(String file, AndroidView root, boolean isSys) {
    Document doc;
    try {
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      dbFactory.setNamespaceAware(true);
      DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
      doc = dBuilder.parse(file);
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
    Element rootElement = doc.getDocumentElement();
    // In older versions, Preference could be put in layout folder and we do
    // not support Prefernce yet.
    if (rootElement.getTagName().equals("PreferenceScreen")) {
      return;
    }
    LinkedList<Pair<Node, AndroidView>> work = Lists.newLinkedList();
    work.add(new Pair<Node, AndroidView>(rootElement, root));
    while (!work.isEmpty()) {
      Pair<Node, AndroidView> p = work.removeFirst();
      Node node = p.getO1();
      AndroidView view = p.getO2();
      view.setOrigin(file);
      NamedNodeMap attrMap = node.getAttributes();
      if (attrMap == null) {
        Logger.verb(this.getClass().getSimpleName(), file + "!!!" + node.getClass() + "!!!"
                + node.toString() + "!!!" + node.getTextContent());
      }
      // Retrieve view id (android:id)
      //Node idNode = attrMap.getNamedItem(ID_ATTR);
      Node idNode = attrMap.getNamedItemNS(ANDROID_NS, "id");
      int guiId = -1;
      String id = null;
      if (idNode != null) {
        String txt = idNode.getTextContent();
        Pair<String, Integer> pair = parseAndroidId(txt, isSys);
        id = pair.getO1();
        Integer guiIdObj = pair.getO2();
        if (guiIdObj == null) {
          if (!isSys) {
            Logger.warn(this.getClass().getSimpleName(),
                    "unresolved android:id " + id + " in "
                    + file);
          }
        } else {
          guiId = guiIdObj.intValue();
          if (lookupNameInGeneralMap("id", guiId, isSys) == null) {
            extraId2ViewMap.put(guiIdObj, view);
          }
        }
      }
      // Retrieve view type
      String guiName = node.getNodeName();
      if ("view".equals(guiName)) {
        // view without class attribute.
        // It does happen.
        if (attrMap.getNamedItem("class") == null)
          continue;
        guiName = attrMap.getNamedItem("class").getTextContent();
      } else if (guiName.equals("MenuItemView")) {
        // FIXME(tony): this is an "approximation".
        guiName = "android.view.MenuItem";
      }
      if (debug) {
        Logger.verb(this.getClass().getSimpleName(), guiName + " (" + guiId + ", " + id + ")");
      }
      //Retrieve callback (android:onClick)
      String callback = readAndroidCallback(attrMap, "onClick");
      if (callback != null) {
        view.setInlineClickHandler(callback);
      }
      // Retrieve text (android:text)
      String text = readAndroidTextOrTitle(attrMap, "text");
      // hailong: add hint support
      // Retrieve hint (android:hint)
      String hint = readAndroidTextOrTitle(attrMap, "hint");
      String autofillHints = readAndroidTextOrTitle(attrMap, "autofillHints");
      if (hint != null && autofillHints != null) hint += PropertyManager.SEPARATOR + autofillHints;
      else if (autofillHints != null) hint = autofillHints;
      if(attrMap.getNamedItem("app:menu") != null){
        String menuName = attrMap.getNamedItem("app:menu").getTextContent();
        if (menuName != null) {
          Pair<String, Integer> pair = parseAndroidId(menuName, isSys);
          Integer menuId = pair.getO2();
          if (menuId != null) {
            view.addAttr(AndroidView.Type.APP_MENU, String.valueOf(menuId));
            menuId2View.put(menuId, view);
          }
        }
        if (attrMap.getNamedItem("app:headerLayout") != null) {
          String headerName = attrMap.getNamedItem("app:headerLayout").getTextContent();
          if (headerName != null) {
            Pair<String, Integer> pair = parseAndroidId(headerName, isSys);
            Integer headerId = pair.getO2();
            if (headerId != null) {
              view.addAttr(AndroidView.Type.APP_HEADER_LAYOUT, String.valueOf(headerId));
              menuHeaderId2View.put(headerId, view);
            }
          }
        }
      }
      if ("fragment".equals(guiName)) {
        Node fragmentNodeAttr = attrMap.getNamedItem("android:name");
        if (fragmentNodeAttr != null) {
          String fragmentClass = fragmentNodeAttr.getTextContent();
          if (fragmentClass.isEmpty()) {
            fragmentNodeAttr = attrMap.getNamedItem("class");
            if (fragmentNodeAttr != null) {
              fragmentClass = fragmentNodeAttr.getTextContent();
            }
          }
          view.addAttr(AndroidView.Type.ANDROID_NAME, fragmentClass);
        }
      }
      String tooltip = readAndroidTextOrTitle(attrMap, "tooltipText");
      String contentDescription = readAndroidTextOrTitle(attrMap, "contentDescription");
      if (hint != null && autofillHints != null) hint += PropertyManager.SEPARATOR + autofillHints;
      else if (autofillHints != null) hint = autofillHints;
      String images = readAndroidImageResource(attrMap);
      view.save(guiId, text, hint, tooltip, contentDescription, images, guiName);
      //view.save(guiId, text, hint, guiName);
      NodeList children = node.getChildNodes();
      for (int i = 0; i < children.getLength(); i++) {
        Node newNode = children.item(i);
        String nodeName = newNode.getNodeName();
        if ("#comment".equals(nodeName)) {
          continue;
        }
        if ("#text".equals(nodeName)) {
          // possible for XML files created on a different operating system
          // than the one our analysis is run on
          continue;
        }
        if (nodeName.equals("requestFocus")) {
          continue;
        }
        if (!newNode.hasAttributes() && !"TableRow".equals(nodeName)
                && !"View".equals(nodeName)) {
          Logger.warn(this.getClass().getSimpleName(),
                  "no attribute node "
                  + newNode.getNodeName());
          continue;
        }
        if (newNode.getNodeName().equals("include")) {
          attrMap = newNode.getAttributes();
          if (attrMap.getNamedItem("layout") == null) {
            Logger.warn("XML", "layout not exist in include");
            for (int j = 0; j < attrMap.getLength(); j++) {
              Logger.trace("XML", attrMap.item(j).getNodeName());
            }
            Logger.trace("XML", "filename" + file);
            continue;
          }
          String layoutTxt = attrMap.getNamedItem("layout").getTextContent();
          String layoutId = null;
          if (layoutTxt.startsWith("@layout/")) {
            layoutId = layoutTxt.substring("@layout/".length());
          } else if (layoutTxt.startsWith("@android:layout/")) {
            layoutId = layoutTxt.substring("@android:layout/".length());
          } else if (layoutTxt.matches("@\\*android:layout\\/(\\w)+")) {
            layoutId = layoutTxt.substring("@*android:layout/".length());
          } else {
            throw new RuntimeException("[WARNING] Unhandled layout id "
                    + layoutTxt + "," + file);
          }
          Integer includeeId = null;
          id = null;
          idNode = attrMap.getNamedItemNS(ANDROID_NS, "id");
          if (idNode != null) {
            String txt = idNode.getTextContent();
            Pair<String, Integer> pair = parseAndroidId(txt, isSys);
            id = pair.getO1();
            Integer guiIdObj = pair.getO2();
            if (guiIdObj == null) {
              if (!isSys) {
                Logger.warn(this.getClass().getSimpleName(),
                        "unresolved android:id " + id
                        + " in " + file);
              }
            } else {
              includeeId = guiIdObj;
            }
          }
          // view.saveInclude(layoutId, includeeId);
          IncludeAndroidView iav = new IncludeAndroidView(layoutId, includeeId);
          iav.setParent(view);
        } else {
          AndroidView newView = new AndroidView();
          newView.setParent(view);
          work.add(new Pair<Node, AndroidView>(newNode, newView));
        }
      }
    }
  }
}
}
// Retrieve view type
String guiName = node.getNodeName();
if ("view".equals(guiName)) {
"unresolved android:id " + id + " in "
+ file);
}
} else {
guiId = guiIdObj.intValue();
if (lookupNameInGeneralMap("id", guiId, isSys) == null) {
extraId2ViewMap.put(guiIdObj, view);
}
}
}
// Retrieve view type
String guiName = node.getNodeName();
if ("view".equals(guiName)) {
// view without class attribute.
// It does happen.
if (attrMap.getNamedItem("class") == null)
continue;
guiName = attrMap.getNamedItem("class").getTextContent();
} else if (guiName.equals("MenuItemView")) {
// FIXME(tony): this is an "approximation".
guiName = "android.view.MenuItem";
int guiId = -1;
String id = null;
if (idNode != null) {
String txt = idNode.getTextContent();
Pair<String, Integer> pair = parseAndroidId(txt, isSys);
id = pair.getO1();
Integer guiIdObj = pair.getO2();
if (guiIdObj == null) {
if (!isSys) {
Logger.warn(this.getClass().getSimpleName(),
"unresolved android:id " + id + " in "
+ file);
}
} else {
guiId = guiIdObj.intValue();
if (lookupNameInGeneralMap("id", guiId, isSys) == null) {
extraId2ViewMap.put(guiIdObj, view);
}
}
}
// Retrieve view type
String guiName = node.getNodeName();
if ("view".equals(guiName)) {
// view without class attribute.
// It does happen.
if (attrMap.getNamedItem("class") == null)
continue;
guiName = attrMap.getNamedItem("class").getTextContent();
} else if (guiName.equals("MenuItemView")) {
// FIXME(tony): this is an "approximation".
guiName = "android.view.MenuItem";
}
if (debug) {
Logger.verb(this.getClass().getSimpleName(), guiName + " (" + guiId + ", " + id + ")");
}
//Retrieve callback (android:onClick)
String callback = readAndroidCallback(attrMap, "onClick");
if (callback != null) {
view.setInlineClickHandler(callback);
}
// Retrieve text (android:text)