TextUtilsCompat.java revision b2583ec3dec093e21ef3b2f9f9174328ab61e9ee
177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/*
277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project
377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *
477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * you may not use this file except in compliance with the License.
677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * You may obtain a copy of the License at
777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *
877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *
1077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
1177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
1277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * See the License for the specific language governing permissions and
1477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * limitations under the License.
1577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */
1677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
1777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopackage android.support.v4.text;
1877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
199c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagiimport android.os.Build;
20a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbyeimport android.support.annotation.NonNull;
21a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbyeimport android.support.annotation.Nullable;
2277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.view.ViewCompat;
2377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport java.util.Locale;
2577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
26c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banespublic final class TextUtilsCompat {
279c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    private static class TextUtilsCompatImpl {
289c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        @NonNull
299c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public String htmlEncode(@NonNull String s) {
309c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            StringBuilder sb = new StringBuilder();
319c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            char c;
329c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            for (int i = 0; i < s.length(); i++) {
339c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                c = s.charAt(i);
349c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                switch (c) {
359c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '<':
369c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&lt;"); //$NON-NLS-1$
379c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
389c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '>':
399c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&gt;"); //$NON-NLS-1$
409c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
419c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '&':
429c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&amp;"); //$NON-NLS-1$
439c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
449c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '\'':
459c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        //http://www.w3.org/TR/xhtml1
469c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // The named character reference &apos; (the apostrophe, U+0027) was
479c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // introduced in XML 1.0 but does not appear in HTML. Authors should
489c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // therefore use &#39; instead of &apos; to work as expected in HTML 4
499c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // user agents.
509c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&#39;"); //$NON-NLS-1$
519c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
529c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '"':
539c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&quot;"); //$NON-NLS-1$
549c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
559c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    default:
569c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append(c);
579c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                }
589c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            }
599c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return sb.toString();
609c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
619c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
629c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public int getLayoutDirectionFromLocale(@Nullable Locale locale) {
639c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            if (locale != null && !locale.equals(ROOT)) {
649c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                final String scriptSubtag = ICUCompat.maximizeAndGetScript(locale);
659c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale);
669c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
67b2583ec3dec093e21ef3b2f9f9174328ab61e9eeRoozbeh Pournader                // This is intentionally limited to Arabic and Hebrew scripts, since older
68b2583ec3dec093e21ef3b2f9f9174328ab61e9eeRoozbeh Pournader                // versions of Android platform only considered those scripts to be right-to-left.
699c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG) ||
709c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) {
719c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    return ViewCompat.LAYOUT_DIRECTION_RTL;
729c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                }
739c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            }
749c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return ViewCompat.LAYOUT_DIRECTION_LTR;
759c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
769c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
779c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        /**
789c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * Fallback algorithm to detect the locale direction. Rely on the first char of the
799c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * localized locale name. This will not work if the localized locale name is in English
809c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * (this is the case for ICU 4.4 and "Urdu" script)
819c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         *
829c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * @param locale
839c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * @return the layout direction. This may be one of:
849c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
859c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
869c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         *
879c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * Be careful: this code will need to be updated when vertical scripts will be supported
889c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         */
899c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        private static int getLayoutDirectionFromFirstChar(@NonNull Locale locale) {
909c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
919c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
929c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
939c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    return ViewCompat.LAYOUT_DIRECTION_RTL;
949c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
959c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
969c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                default:
979c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    return ViewCompat.LAYOUT_DIRECTION_LTR;
989c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            }
999c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1009c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    }
1019c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
1029c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    private static class TextUtilsCompatJellybeanMr1Impl extends TextUtilsCompatImpl {
10315375aa6fd54b036f97f99229aefab2822c8a1c9Aurimas Liutikas        @Override
1049c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        @NonNull
1059c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public String htmlEncode(@NonNull String s) {
1069c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return TextUtilsCompatJellybeanMr1.htmlEncode(s);
1079c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1089c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
1099c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        @Override
1109c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public int getLayoutDirectionFromLocale(@Nullable Locale locale) {
1119c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return TextUtilsCompatJellybeanMr1.getLayoutDirectionFromLocale(locale);
1129c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1139c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    }
1149c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
1159c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    private static final TextUtilsCompatImpl IMPL;
1169c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    static {
1179c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        final int version = Build.VERSION.SDK_INT;
1189c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        if (version >= 17) { // JellyBean MR1
1199c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            IMPL = new TextUtilsCompatJellybeanMr1Impl();
1209c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        } else {
1219c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            IMPL = new TextUtilsCompatImpl();
1229c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1239c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    }
12477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
12577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
12677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Html-encode the string.
12777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param s the string to be encoded
12877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return the encoded string
12977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
130a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbye    @NonNull
131a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbye    public static String htmlEncode(@NonNull String s) {
1329c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        return IMPL.htmlEncode(s);
13377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
13477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
13577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
13677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Return the layout direction for a given Locale
13777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
13877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale the Locale for which we want the layout direction. Can be null.
13977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return the layout direction. This may be one of:
14077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
14177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
14277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
14377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Be careful: this code will need to be updated when vertical scripts will be supported
14477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
145a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbye    public static int getLayoutDirectionFromLocale(@Nullable Locale locale) {
1469c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        return IMPL.getLayoutDirectionFromLocale(locale);
14777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
14877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
14977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static final Locale ROOT = new Locale("", "");
15077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
15177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static String ARAB_SCRIPT_SUBTAG = "Arab";
15277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static String HEBR_SCRIPT_SUBTAG = "Hebr";
153c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banes
154c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banes    private TextUtilsCompat() {}
15577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}
156