AccessibilityNodeInfoDumper.java revision 4f68d87218ee618670a71adbaaca83d76a2e2d37
1e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu/*
2e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * Copyright (C) 2012 The Android Open Source Project
3e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu *
4e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * Licensed under the Apache License, Version 2.0 (the "License");
5e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * you may not use this file except in compliance with the License.
6e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * You may obtain a copy of the License at
7e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu *
8e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu *      http://www.apache.org/licenses/LICENSE-2.0
9e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu *
10e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * Unless required by applicable law or agreed to in writing, software
11e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * distributed under the License is distributed on an "AS IS" BASIS,
12e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * See the License for the specific language governing permissions and
14e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu * limitations under the License.
15e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu */
16e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
17e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhupackage com.android.uiautomator.core;
18e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
196a6ac2e1847fd2d2ce389e61ed723d81d6d2d229Guang Zhuimport android.hardware.display.DisplayManagerGlobal;
20e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport android.os.Environment;
21e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport android.os.SystemClock;
22e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport android.util.Log;
23e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport android.util.Xml;
246a6ac2e1847fd2d2ce389e61ed723d81d6d2d229Guang Zhuimport android.view.Display;
25e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport android.view.accessibility.AccessibilityNodeInfo;
26e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
27e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport java.io.File;
28e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport java.io.FileWriter;
29e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport java.io.IOException;
30e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhuimport java.io.StringWriter;
31e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
327f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtazimport org.xmlpull.v1.XmlSerializer;
337f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz
34ddc1008f06fd2a875037026490ce1f848a442572Guang Zhu/**
35ddc1008f06fd2a875037026490ce1f848a442572Guang Zhu *
36ddc1008f06fd2a875037026490ce1f848a442572Guang Zhu * @hide
37ddc1008f06fd2a875037026490ce1f848a442572Guang Zhu */
38e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhupublic class AccessibilityNodeInfoDumper {
39e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
40e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName();
417f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz    private static final String[] NAF_EXCLUDED_CLASSES = new String[] {
426c66df53c880e480c8016ebf846672b49aa10ec8Adam Momtaz            android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(),
436c66df53c880e480c8016ebf846672b49aa10ec8Adam Momtaz            android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName()
447f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz    };
45e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
46e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    /**
47e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
48e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * and generates an xml dump into the /data/local/window_dump.xml
49e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * @param info
50e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     */
51e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    public static void dumpWindowToFile(AccessibilityNodeInfo info) {
52e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        File baseDir = new File(Environment.getDataDirectory(), "local");
53e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        if (!baseDir.exists()) {
54e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            baseDir.mkdir();
55e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            baseDir.setExecutable(true, false);
56e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            baseDir.setWritable(true, false);
57e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            baseDir.setReadable(true, false);
58e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
59e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        dumpWindowToFile(info, new File(
60e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                new File(Environment.getDataDirectory(), "local"), "window_dump.xml"));
61e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    }
62e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
63e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    /**
64e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
65e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * and generates an xml dump to the location specified by <code>dumpFile</code>
66e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * @param info
67e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     */
68e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile) {
69e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        if (root == null) {
70e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            return;
71e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
72e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        final long startTime = SystemClock.uptimeMillis();
73e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        try {
74e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            FileWriter writer = new FileWriter(dumpFile);
75e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            XmlSerializer serializer = Xml.newSerializer();
76e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            StringWriter stringWriter = new StringWriter();
77e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            serializer.setOutput(stringWriter);
78e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            serializer.startDocument("UTF-8", true);
79e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            serializer.startTag("", "hierarchy");
806a6ac2e1847fd2d2ce389e61ed723d81d6d2d229Guang Zhu            serializer.attribute("", "rotation", Integer.toString(
816a6ac2e1847fd2d2ce389e61ed723d81d6d2d229Guang Zhu                    DisplayManagerGlobal.getInstance().getRealDisplay(
826a6ac2e1847fd2d2ce389e61ed723d81d6d2d229Guang Zhu                            Display.DEFAULT_DISPLAY).getRotation()));
83e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            dumpNodeRec(root, serializer, 0);
84e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            serializer.endTag("", "hierarchy");
85e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            serializer.endDocument();
86e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            writer.write(stringWriter.toString());
87e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            writer.close();
88e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        } catch (IOException e) {
89e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            Log.e(LOGTAG, "failed to dump window to file", e);
90e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
91e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        final long endTime = SystemClock.uptimeMillis();
92e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
93e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    }
94e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
954f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,
966c66df53c880e480c8016ebf846672b49aa10ec8Adam Momtaz            int index) throws IOException {
976c66df53c880e480c8016ebf846672b49aa10ec8Adam Momtaz        serializer.startTag("", "node");
984f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        if (!nafExcludedClass(node) && !nafCheck(node))
994f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz            serializer.attribute("", "NAF", Boolean.toString(true));
100e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "index", Integer.toString(index));
1014f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        serializer.attribute("", "text", safeCharSeqToString(node.getText()));
102e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
103e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
1044f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
105e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
106e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
1074f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
1084f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
109e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
110e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
111e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
112e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
113e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "password", Boolean.toString(node.isPassword()));
114e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
1157f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz        serializer.attribute("", "bounds",
1167f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz                AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node).toShortString());
117e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        int count = node.getChildCount();
118e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        for (int i = 0; i < count; i++) {
119e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            AccessibilityNodeInfo child = node.getChild(i);
120e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            if (child != null) {
121e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                if (child.isVisibleToUser()) {
1224f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz                    dumpNodeRec(child, serializer, i);
1237f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz                    child.recycle();
124e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                } else {
125e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
126e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                }
127e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            } else {
128e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
129e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                        i, count, node.toString()));
130e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            }
131e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
132e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        serializer.endTag("", "node");
133e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    }
134e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
135e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    /**
1367f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz     * The list of classes to exclude my not be complete. We're attempting to
1377f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz     * only reduce noise from standard layout classes that may be falsely
1387f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz     * configured to accept clicks and are also enabled.
1397f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz     *
140e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * @param n
141e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     * @return
142e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu     */
1437f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz    private static boolean nafExcludedClass(AccessibilityNodeInfo n) {
144e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        String className = safeCharSeqToString(n.getClassName());
1457f38ef08d07295967b905a61b2356ff6cdf31159Adam Momtaz        for(String excludedClassName : NAF_EXCLUDED_CLASSES) {
146e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            if(className.endsWith(excludedClassName))
147e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                return true;
148e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
149e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        return false;
150e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    }
151e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
1524f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    /**
1534f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * We're looking for UI controls that are enabled, clickable but have no
1544f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * text nor content-description. Such controls configuration indicate an
1554f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * interactive control is present in the UI and is most likely not
1564f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * accessibility friendly. We refer to such controls here as NAF controls
1574f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * (Not Accessibility Friendly)
1584f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     *
1594f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * @param node
1604f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * @return false if a node fails the check, true if all is OK
1614f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     */
1624f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    private static boolean nafCheck(AccessibilityNodeInfo node) {
1634f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        boolean isNaf = node.isClickable() && node.isEnabled()
1644f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz                && safeCharSeqToString(node.getContentDescription()).isEmpty()
1654f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz                && safeCharSeqToString(node.getText()).isEmpty();
1664f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz
1674f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        if (!isNaf)
1684f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz            return true;
1694f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz
1704f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        // check children since sometimes the containing element is clickable
1714f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        // and NAF but a child's text or description is available. Will assume
1724f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        // such layout as fine.
1734f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        return childNafCheck(node);
1744f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    }
1754f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz
1764f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    /**
1774f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * This should be used when it's already determined that the node is NAF and
1784f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * a further check of its children is in order. A node maybe a container
1794f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * such as LinerLayout and may be set to be clickable but have no text or
1804f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * content description but it is counting on one of its children to fulfill
1814f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * the requirement for being accessibility friendly by having one or more of
1824f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * its children fill the text or content-description. Such a combination is
1834f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * considered by this dumper as acceptable for accessibility.
1844f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     *
1854f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * @param node
1864f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     * @return
1874f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz     */
1884f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    private static boolean childNafCheck(AccessibilityNodeInfo node) {
1894f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        int childCount = node.getChildCount();
1904f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        for (int x = 0; x < childCount; x++) {
1914f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz            AccessibilityNodeInfo childNode = node.getChild(x);
1924f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz
1934f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz            if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
1944f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz                    || !safeCharSeqToString(childNode.getText()).isEmpty())
1954f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz                return true;
1964f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz
1974f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz            return childNafCheck(childNode);
1984f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        }
1994f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz        return false;
2004f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz    }
2014f68d87218ee618670a71adbaaca83d76a2e2d37Adam Momtaz
202e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    private static String safeCharSeqToString(CharSequence cs) {
203e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        if (cs == null)
204e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            return "";
205e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        else {
206e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            return stripInvalidXMLChars(cs);
207e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
208e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    }
209e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
210e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    private static String stripInvalidXMLChars(CharSequence cs) {
211e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        StringBuffer ret = new StringBuffer();
212e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        char ch;
213e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        /* http://www.w3.org/TR/xml11/#charsets
214e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
215e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
216e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
217e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
218e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
219e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
220e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        [#x10FFFE-#x10FFFF].
221e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu         */
222e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        for (int i = 0; i < cs.length(); i++) {
223e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            ch = cs.charAt(i);
224e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu
225e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            if((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
226e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
227e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
228e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
229e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
230e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
231e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
232e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
233e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
234e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
235e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                    (ch >= 0x10FFFE && ch <= 0x10FFFF))
236e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                ret.append(".");
237e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu            else
238e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu                ret.append(ch);
239e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        }
240e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu        return ret.toString();
241e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu    }
242e54d649fb83a0a44516e5c25a9ac1992c8950e59Guang Zhu}
243