1ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu/*
2ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * Copyright (C) 2012 The Android Open Source Project
3ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu *
4ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * Licensed under the Apache License, Version 2.0 (the "License");
5ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * you may not use this file except in compliance with the License.
6ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * You may obtain a copy of the License at
7ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu *
8ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu *      http://www.apache.org/licenses/LICENSE-2.0
9ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu *
10ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * Unless required by applicable law or agreed to in writing, software
11ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * distributed under the License is distributed on an "AS IS" BASIS,
12ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * See the License for the specific language governing permissions and
14ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu * limitations under the License.
15ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu */
16ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
17ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhupackage com.android.uiautomator.core;
18ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
19ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport android.os.Environment;
20ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport android.os.SystemClock;
21ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport android.util.Log;
22ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport android.util.Xml;
23ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport android.view.accessibility.AccessibilityNodeInfo;
24ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
2547d54d40309083809bf04ba07a5b7191fbca9351Guang Zhuimport org.xmlpull.v1.XmlSerializer;
2647d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu
27ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport java.io.File;
28ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport java.io.FileWriter;
29ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport java.io.IOException;
30ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhuimport java.io.StringWriter;
31ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
325a5e8ca8a086bd87b7753cbd3c46ff1ca72292a2Guang Zhu/**
335a5e8ca8a086bd87b7753cbd3c46ff1ca72292a2Guang Zhu *
345a5e8ca8a086bd87b7753cbd3c46ff1ca72292a2Guang Zhu * @hide
355a5e8ca8a086bd87b7753cbd3c46ff1ca72292a2Guang Zhu */
36ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhupublic class AccessibilityNodeInfoDumper {
37ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
38ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName();
3973da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz    private static final String[] NAF_EXCLUDED_CLASSES = new String[] {
40171367865fbf1bb9e0abae552da998e47c4cc817Adam Momtaz            android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(),
41171367865fbf1bb9e0abae552da998e47c4cc817Adam Momtaz            android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName()
4273da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz    };
43ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
44ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    /**
45ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu     * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
46ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu     * and generates an xml dump to the location specified by <code>dumpFile</code>
475478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le     * @param root The root accessibility node.
485478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le     * @param dumpFile The file to dump to.
4947d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu     * @param rotation The rotaion of current display
5047d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu     * @param width The pixel width of current display
5147d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu     * @param height The pixel height of current display
52ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu     */
5347d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu    public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile, int rotation,
5447d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu            int width, int height) {
55ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        if (root == null) {
56ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            return;
57ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        }
58ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        final long startTime = SystemClock.uptimeMillis();
59ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        try {
60ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            FileWriter writer = new FileWriter(dumpFile);
61ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            XmlSerializer serializer = Xml.newSerializer();
62ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            StringWriter stringWriter = new StringWriter();
63ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            serializer.setOutput(stringWriter);
64ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            serializer.startDocument("UTF-8", true);
65ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            serializer.startTag("", "hierarchy");
6647d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu            serializer.attribute("", "rotation", Integer.toString(rotation));
6747d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu            dumpNodeRec(root, serializer, 0, width, height);
68ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            serializer.endTag("", "hierarchy");
69ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            serializer.endDocument();
70ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            writer.write(stringWriter.toString());
71ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            writer.close();
72ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        } catch (IOException e) {
73ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            Log.e(LOGTAG, "failed to dump window to file", e);
74ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        }
75ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        final long endTime = SystemClock.uptimeMillis();
76ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
77ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    }
78ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
7947d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu    private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index,
8047d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu            int width, int height) throws IOException {
81171367865fbf1bb9e0abae552da998e47c4cc817Adam Momtaz        serializer.startTag("", "node");
8292b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        if (!nafExcludedClass(node) && !nafCheck(node))
8392b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz            serializer.attribute("", "NAF", Boolean.toString(true));
84ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "index", Integer.toString(index));
8592b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        serializer.attribute("", "text", safeCharSeqToString(node.getText()));
86f006ec43aebace3eb45a8ea94c2bdedd29cc5416Svetoslav        serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
87ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
88ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
8992b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
90ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
91ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
9292b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
9392b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
94ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
95ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
96ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
97ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
98ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "password", Boolean.toString(node.isPassword()));
99ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
10047d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu        serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
10147d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu                node, width, height).toShortString());
102ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        int count = node.getChildCount();
103ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        for (int i = 0; i < count; i++) {
104ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            AccessibilityNodeInfo child = node.getChild(i);
105ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            if (child != null) {
106ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                if (child.isVisibleToUser()) {
10747d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu                    dumpNodeRec(child, serializer, i, width, height);
10873da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz                    child.recycle();
109ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                } else {
110ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
111ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                }
112ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            } else {
113ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
114ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                        i, count, node.toString()));
115ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            }
116ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        }
117ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        serializer.endTag("", "node");
118ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    }
119ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
120ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    /**
12173da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz     * The list of classes to exclude my not be complete. We're attempting to
12273da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz     * only reduce noise from standard layout classes that may be falsely
12373da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz     * configured to accept clicks and are also enabled.
12473da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz     *
1255478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le     * @param node
1265478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le     * @return true if node is excluded.
127ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu     */
1285478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le    private static boolean nafExcludedClass(AccessibilityNodeInfo node) {
1295478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le        String className = safeCharSeqToString(node.getClassName());
13073da7f69437c911dc60cc3b9ad7ad876677c8f14Adam Momtaz        for(String excludedClassName : NAF_EXCLUDED_CLASSES) {
131ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            if(className.endsWith(excludedClassName))
132ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                return true;
133ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        }
134ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        return false;
135ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    }
136ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
13792b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz    /**
13892b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * We're looking for UI controls that are enabled, clickable but have no
13992b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * text nor content-description. Such controls configuration indicate an
14092b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * interactive control is present in the UI and is most likely not
14192b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * accessibility friendly. We refer to such controls here as NAF controls
14292b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * (Not Accessibility Friendly)
14392b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     *
14492b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * @param node
14592b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * @return false if a node fails the check, true if all is OK
14692b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     */
14792b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz    private static boolean nafCheck(AccessibilityNodeInfo node) {
14892b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        boolean isNaf = node.isClickable() && node.isEnabled()
14992b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz                && safeCharSeqToString(node.getContentDescription()).isEmpty()
15092b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz                && safeCharSeqToString(node.getText()).isEmpty();
15192b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz
15292b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        if (!isNaf)
15392b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz            return true;
15492b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz
15592b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        // check children since sometimes the containing element is clickable
15692b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        // and NAF but a child's text or description is available. Will assume
15792b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        // such layout as fine.
15892b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        return childNafCheck(node);
15992b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz    }
16092b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz
16192b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz    /**
16292b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * This should be used when it's already determined that the node is NAF and
16392b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * a further check of its children is in order. A node maybe a container
16492b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * such as LinerLayout and may be set to be clickable but have no text or
16592b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * content description but it is counting on one of its children to fulfill
16692b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * the requirement for being accessibility friendly by having one or more of
16792b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * its children fill the text or content-description. Such a combination is
16892b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * considered by this dumper as acceptable for accessibility.
16992b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     *
17092b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     * @param node
1715478840ad89ace2474b9b4d3ab14b92d9e06bf47Thanh Le     * @return false if node fails the check.
17292b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz     */
17392b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz    private static boolean childNafCheck(AccessibilityNodeInfo node) {
17492b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        int childCount = node.getChildCount();
17592b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        for (int x = 0; x < childCount; x++) {
17692b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz            AccessibilityNodeInfo childNode = node.getChild(x);
17792b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz
17892b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz            if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
17992b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz                    || !safeCharSeqToString(childNode.getText()).isEmpty())
18092b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz                return true;
18192b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz
182125a7bcd4966779ab02f21a4ef6ddf88646d8bf6Adam Momtaz            if (childNafCheck(childNode))
183125a7bcd4966779ab02f21a4ef6ddf88646d8bf6Adam Momtaz                return true;
18492b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        }
18592b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz        return false;
18692b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz    }
18792b8580fa210008883791bad05e9b912d73a98a6Adam Momtaz
188ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    private static String safeCharSeqToString(CharSequence cs) {
189ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        if (cs == null)
190ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            return "";
191ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        else {
192ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            return stripInvalidXMLChars(cs);
193ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        }
194ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    }
195ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
196ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    private static String stripInvalidXMLChars(CharSequence cs) {
197ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        StringBuffer ret = new StringBuffer();
198ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        char ch;
199ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        /* http://www.w3.org/TR/xml11/#charsets
200ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
201ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
202ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
203ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
204ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
205ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
206ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        [#x10FFFE-#x10FFFF].
207ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu         */
208ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        for (int i = 0; i < cs.length(); i++) {
209ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            ch = cs.charAt(i);
210ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu
211ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            if((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
212ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
213ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
214ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
215ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
216ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
217ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
218ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
219ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
220ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
221ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                    (ch >= 0x10FFFE && ch <= 0x10FFFF))
222ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                ret.append(".");
223ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu            else
224ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu                ret.append(ch);
225ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        }
226ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu        return ret.toString();
227ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu    }
228ff763316cdb1986d8668ca3011cbb892b43aab93Guang Zhu}
229