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 {
28552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas        TextUtilsCompatImpl() {
29552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas        }
30552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas
319c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        @NonNull
329c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public String htmlEncode(@NonNull String s) {
339c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            StringBuilder sb = new StringBuilder();
349c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            char c;
359c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            for (int i = 0; i < s.length(); i++) {
369c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                c = s.charAt(i);
379c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                switch (c) {
389c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '<':
399c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&lt;"); //$NON-NLS-1$
409c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
419c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '>':
429c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&gt;"); //$NON-NLS-1$
439c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
449c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '&':
459c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&amp;"); //$NON-NLS-1$
469c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
479c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '\'':
489c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        //http://www.w3.org/TR/xhtml1
499c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // The named character reference &apos; (the apostrophe, U+0027) was
509c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // introduced in XML 1.0 but does not appear in HTML. Authors should
519c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // therefore use &#39; instead of &apos; to work as expected in HTML 4
529c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        // user agents.
539c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&#39;"); //$NON-NLS-1$
549c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
559c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    case '"':
569c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append("&quot;"); //$NON-NLS-1$
579c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        break;
589c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    default:
599c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        sb.append(c);
609c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                }
619c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            }
629c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return sb.toString();
639c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
649c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
659c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public int getLayoutDirectionFromLocale(@Nullable Locale locale) {
669c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            if (locale != null && !locale.equals(ROOT)) {
679c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                final String scriptSubtag = ICUCompat.maximizeAndGetScript(locale);
689c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale);
699c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
70b2583ec3dec093e21ef3b2f9f9174328ab61e9eeRoozbeh Pournader                // This is intentionally limited to Arabic and Hebrew scripts, since older
71b2583ec3dec093e21ef3b2f9f9174328ab61e9eeRoozbeh Pournader                // versions of Android platform only considered those scripts to be right-to-left.
729c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG) ||
739c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                        scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) {
749c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    return ViewCompat.LAYOUT_DIRECTION_RTL;
759c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                }
769c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            }
779c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return ViewCompat.LAYOUT_DIRECTION_LTR;
789c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
799c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
809c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        /**
819c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * Fallback algorithm to detect the locale direction. Rely on the first char of the
829c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * localized locale name. This will not work if the localized locale name is in English
839c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * (this is the case for ICU 4.4 and "Urdu" script)
849c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         *
859c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * @param locale
869c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * @return the layout direction. This may be one of:
879c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
889c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
899c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         *
909c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         * Be careful: this code will need to be updated when vertical scripts will be supported
919c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi         */
929c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        private static int getLayoutDirectionFromFirstChar(@NonNull Locale locale) {
939c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
949c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
959c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
969c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    return ViewCompat.LAYOUT_DIRECTION_RTL;
979c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
989c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
999c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                default:
1009c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi                    return ViewCompat.LAYOUT_DIRECTION_LTR;
1019c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            }
1029c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1039c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    }
1049c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
1059c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    private static class TextUtilsCompatJellybeanMr1Impl extends TextUtilsCompatImpl {
106552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas        TextUtilsCompatJellybeanMr1Impl() {
107552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas        }
108552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas
10915375aa6fd54b036f97f99229aefab2822c8a1c9Aurimas Liutikas        @Override
1109c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        @NonNull
1119c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public String htmlEncode(@NonNull String s) {
1129c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return TextUtilsCompatJellybeanMr1.htmlEncode(s);
1139c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1149c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
1159c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        @Override
1169c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        public int getLayoutDirectionFromLocale(@Nullable Locale locale) {
1179c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            return TextUtilsCompatJellybeanMr1.getLayoutDirectionFromLocale(locale);
1189c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1199c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    }
1209c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi
1219c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    private static final TextUtilsCompatImpl IMPL;
1229c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    static {
1239c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        final int version = Build.VERSION.SDK_INT;
1249c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        if (version >= 17) { // JellyBean MR1
1259c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            IMPL = new TextUtilsCompatJellybeanMr1Impl();
1269c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        } else {
1279c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi            IMPL = new TextUtilsCompatImpl();
1289c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        }
1299c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi    }
13077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
13177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
13277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Html-encode the string.
13377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param s the string to be encoded
13477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return the encoded string
13577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
136a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbye    @NonNull
137a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbye    public static String htmlEncode(@NonNull String s) {
1389c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        return IMPL.htmlEncode(s);
13977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
14077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
14177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
14277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Return the layout direction for a given Locale
14377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
14477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale the Locale for which we want the layout direction. Can be null.
14577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return the layout direction. This may be one of:
14677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
14777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
14877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
14977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Be careful: this code will need to be updated when vertical scripts will be supported
15077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
151a3ff3273e976adf19770651dcf473fa67b38eb22Tor Norbye    public static int getLayoutDirectionFromLocale(@Nullable Locale locale) {
1529c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi        return IMPL.getLayoutDirectionFromLocale(locale);
15377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
15477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
15577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static final Locale ROOT = new Locale("", "");
15677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
157552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas    static String ARAB_SCRIPT_SUBTAG = "Arab";
158552766fa685c63ad760c92239faaba12e6ad51f1Aurimas Liutikas    static String HEBR_SCRIPT_SUBTAG = "Hebr";
159c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banes
160c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banes    private TextUtilsCompat() {}
16177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}
162