118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu/*
218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Copyright (C) 2012 The Android Open Source Project
318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu *
418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Licensed under the Apache License, Version 2.0 (the "License");
518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * you may not use this file except in compliance with the License.
618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * You may obtain a copy of the License at
718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu *
818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu *      http://www.apache.org/licenses/LICENSE-2.0
918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu *
1018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Unless required by applicable law or agreed to in writing, software
1118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * distributed under the License is distributed on an "AS IS" BASIS,
1218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * See the License for the specific language governing permissions and
1418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * limitations under the License.
1518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu */
1618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
1718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhupackage com.android.uiautomator.core;
1818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
1918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.os.Environment;
2018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.os.SystemClock;
2118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.util.Log;
2218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.util.Xml;
2318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.view.accessibility.AccessibilityNodeInfo;
2418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
2518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport org.xmlpull.v1.XmlSerializer;
2618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
2718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport java.io.File;
2818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport java.io.FileWriter;
2918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport java.io.IOException;
3018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport java.io.StringWriter;
3118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
3218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu/**
3318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu *
3418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * @hide
3518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu */
3618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhupublic class AccessibilityNodeInfoDumper {
3718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
3818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName();
3918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static final String[] NAF_EXCLUDED_CLASSES = new String[] {
4018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(),
4118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName()
4218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    };
4318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
4418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    /**
4518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
4618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * and generates an xml dump into the /data/local/window_dump.xml
4718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param root The root accessibility node.
4818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param rotation The rotaion of current display
4918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param width The pixel width of current display
5018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param height The pixel height of current display
5118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     */
5218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    public static void dumpWindowToFile(AccessibilityNodeInfo root, int rotation,
5318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            int width, int height) {
5418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        File baseDir = new File(Environment.getDataDirectory(), "local");
5518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        if (!baseDir.exists()) {
5618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            baseDir.mkdir();
5718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            baseDir.setExecutable(true, false);
5818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            baseDir.setWritable(true, false);
5918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            baseDir.setReadable(true, false);
6018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
6118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        dumpWindowToFile(root,
6218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                new File(new File(Environment.getDataDirectory(), "local"), "window_dump.xml"),
6318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                rotation, width, height);
6418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
6518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
6618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    /**
6718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
6818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * and generates an xml dump to the location specified by <code>dumpFile</code>
6918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param root The root accessibility node.
7018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param dumpFile The file to dump to.
7118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param rotation The rotaion of current display
7218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param width The pixel width of current display
7318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param height The pixel height of current display
7418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     */
7518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile, int rotation,
7618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            int width, int height) {
7718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        if (root == null) {
7818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            return;
7918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
8018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        final long startTime = SystemClock.uptimeMillis();
8118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        try {
8218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            FileWriter writer = new FileWriter(dumpFile);
8318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            XmlSerializer serializer = Xml.newSerializer();
8418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            StringWriter stringWriter = new StringWriter();
8518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.setOutput(stringWriter);
8618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.startDocument("UTF-8", true);
8718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.startTag("", "hierarchy");
8818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.attribute("", "rotation", Integer.toString(rotation));
8918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            dumpNodeRec(root, serializer, 0, width, height);
9018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.endTag("", "hierarchy");
9118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.endDocument();
9218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            writer.write(stringWriter.toString());
9318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            writer.close();
9418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        } catch (IOException e) {
9518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            Log.e(LOGTAG, "failed to dump window to file", e);
9618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
9718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        final long endTime = SystemClock.uptimeMillis();
9818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
9918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
10018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
10118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index,
10218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            int width, int height) throws IOException {
10318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.startTag("", "node");
10418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        if (!nafExcludedClass(node) && !nafCheck(node))
10518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            serializer.attribute("", "NAF", Boolean.toString(true));
10618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "index", Integer.toString(index));
10718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "text", safeCharSeqToString(node.getText()));
10818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
10918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
11018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
11118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
11218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
11318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
11418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
11518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
11618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
11718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
11818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
11918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
12018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "password", Boolean.toString(node.isPassword()));
12118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
12218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
12318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                node, width, height).toShortString());
12418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        int count = node.getChildCount();
12518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        for (int i = 0; i < count; i++) {
12618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            AccessibilityNodeInfo child = node.getChild(i);
12718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            if (child != null) {
12818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                if (child.isVisibleToUser()) {
12918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    dumpNodeRec(child, serializer, i, width, height);
13018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    child.recycle();
13118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                } else {
13218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
13318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                }
13418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            } else {
13518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
13618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                        i, count, node.toString()));
13718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            }
13818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
13918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        serializer.endTag("", "node");
14018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
14118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
14218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    /**
14318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * The list of classes to exclude my not be complete. We're attempting to
14418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * only reduce noise from standard layout classes that may be falsely
14518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * configured to accept clicks and are also enabled.
14618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     *
14718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param node
14818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @return true if node is excluded.
14918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     */
15018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static boolean nafExcludedClass(AccessibilityNodeInfo node) {
15118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        String className = safeCharSeqToString(node.getClassName());
15218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        for(String excludedClassName : NAF_EXCLUDED_CLASSES) {
15318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            if(className.endsWith(excludedClassName))
15418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                return true;
15518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
15618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        return false;
15718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
15818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
15918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    /**
16018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * We're looking for UI controls that are enabled, clickable but have no
16118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * text nor content-description. Such controls configuration indicate an
16218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * interactive control is present in the UI and is most likely not
16318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * accessibility friendly. We refer to such controls here as NAF controls
16418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * (Not Accessibility Friendly)
16518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     *
16618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param node
16718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @return false if a node fails the check, true if all is OK
16818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     */
16918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static boolean nafCheck(AccessibilityNodeInfo node) {
17018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        boolean isNaf = node.isClickable() && node.isEnabled()
17118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                && safeCharSeqToString(node.getContentDescription()).isEmpty()
17218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                && safeCharSeqToString(node.getText()).isEmpty();
17318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
17418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        if (!isNaf)
17518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            return true;
17618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
17718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        // check children since sometimes the containing element is clickable
17818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        // and NAF but a child's text or description is available. Will assume
17918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        // such layout as fine.
18018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        return childNafCheck(node);
18118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
18218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
18318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    /**
18418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * This should be used when it's already determined that the node is NAF and
18518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * a further check of its children is in order. A node maybe a container
18618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * such as LinerLayout and may be set to be clickable but have no text or
18718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * content description but it is counting on one of its children to fulfill
18818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * the requirement for being accessibility friendly by having one or more of
18918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * its children fill the text or content-description. Such a combination is
19018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * considered by this dumper as acceptable for accessibility.
19118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     *
19218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @param node
19318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     * @return false if node fails the check.
19418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu     */
19518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static boolean childNafCheck(AccessibilityNodeInfo node) {
19618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        int childCount = node.getChildCount();
19718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        for (int x = 0; x < childCount; x++) {
19818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            AccessibilityNodeInfo childNode = node.getChild(x);
19918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
20018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
20118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    || !safeCharSeqToString(childNode.getText()).isEmpty())
20218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                return true;
20318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
20418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            if (childNafCheck(childNode))
20518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                return true;
20618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
20718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        return false;
20818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
20918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
21018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static String safeCharSeqToString(CharSequence cs) {
21118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        if (cs == null)
21218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            return "";
21318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        else {
21418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            return stripInvalidXMLChars(cs);
21518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
21618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
21718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
21818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    private static String stripInvalidXMLChars(CharSequence cs) {
21918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        StringBuffer ret = new StringBuffer();
22018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        char ch;
22118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        /* http://www.w3.org/TR/xml11/#charsets
22218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
22318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
22418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
22518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
22618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
22718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
22818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        [#x10FFFE-#x10FFFF].
22918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu         */
23018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        for (int i = 0; i < cs.length(); i++) {
23118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            ch = cs.charAt(i);
23218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu
23318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            if((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
23418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
23518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
23618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
23718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
23818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
23918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
24018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
24118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
24218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
24318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                    (ch >= 0x10FFFE && ch <= 0x10FFFF))
24418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                ret.append(".");
24518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu            else
24618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu                ret.append(ch);
24718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        }
24818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu        return ret.toString();
24918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu    }
25018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu}
251