1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.core.text;
18
19import static android.os.Build.VERSION.SDK_INT;
20
21import android.text.TextUtils;
22
23import androidx.annotation.NonNull;
24import androidx.annotation.Nullable;
25import androidx.core.view.ViewCompat;
26
27import java.util.Locale;
28
29/**
30 * Backwards compatible version of {@link TextUtils}.
31 */
32public final class TextUtilsCompat {
33    private static final Locale ROOT = new Locale("", "");
34    private static final String ARAB_SCRIPT_SUBTAG = "Arab";
35    private static final String HEBR_SCRIPT_SUBTAG = "Hebr";
36
37    /**
38     * Html-encode the string.
39     *
40     * @param s the string to be encoded
41     * @return the encoded string
42     */
43    @NonNull
44    public static String htmlEncode(@NonNull String s) {
45        if (SDK_INT >= 17) {
46            return TextUtils.htmlEncode(s);
47        } else {
48            StringBuilder sb = new StringBuilder();
49            char c;
50            for (int i = 0; i < s.length(); i++) {
51                c = s.charAt(i);
52                switch (c) {
53                    case '<':
54                        sb.append("&lt;"); //$NON-NLS-1$
55                        break;
56                    case '>':
57                        sb.append("&gt;"); //$NON-NLS-1$
58                        break;
59                    case '&':
60                        sb.append("&amp;"); //$NON-NLS-1$
61                        break;
62                    case '\'':
63                        //http://www.w3.org/TR/xhtml1
64                        // The named character reference &apos; (the apostrophe, U+0027) was
65                        // introduced in XML 1.0 but does not appear in HTML. Authors should
66                        // therefore use &#39; instead of &apos; to work as expected in HTML 4
67                        // user agents.
68                        sb.append("&#39;"); //$NON-NLS-1$
69                        break;
70                    case '"':
71                        sb.append("&quot;"); //$NON-NLS-1$
72                        break;
73                    default:
74                        sb.append(c);
75                }
76            }
77            return sb.toString();
78        }
79    }
80
81    /**
82     * Returns the layout direction for a given Locale
83     *
84     * @param locale the {@link Locale} for which we want the layout direction, maybe be
85     *               {@code null}.
86     * @return the layout direction, either {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
87     *         {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
88     */
89    public static int getLayoutDirectionFromLocale(@Nullable Locale locale) {
90        if (SDK_INT >= 17) {
91            return TextUtils.getLayoutDirectionFromLocale(locale);
92        } else {
93            if (locale != null && !locale.equals(ROOT)) {
94                final String scriptSubtag = ICUCompat.maximizeAndGetScript(locale);
95                if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale);
96
97                // This is intentionally limited to Arabic and Hebrew scripts, since older
98                // versions of Android platform only considered those scripts to be right-to-left.
99                if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG)
100                        || scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) {
101                    return ViewCompat.LAYOUT_DIRECTION_RTL;
102                }
103            }
104            return ViewCompat.LAYOUT_DIRECTION_LTR;
105        }
106    }
107
108    /**
109     * Fallback algorithm to detect the locale direction. Rely on the first char of the
110     * localized locale name. This will not work if the localized locale name is in English
111     * (this is the case for ICU 4.4 and "Urdu" script)
112     *
113     * @param locale the {@link Locale} for which we want the layout direction, maybe be
114     *               {@code null}.
115     * @return the layout direction, either {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
116     *         {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
117     */
118    private static int getLayoutDirectionFromFirstChar(@NonNull Locale locale) {
119        switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
120            case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
121            case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
122                return ViewCompat.LAYOUT_DIRECTION_RTL;
123
124            case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
125            default:
126                return ViewCompat.LAYOUT_DIRECTION_LTR;
127        }
128    }
129
130    private TextUtilsCompat() {}
131}
132