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("<"); //$NON-NLS-1$ 379c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi break; 389c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi case '>': 399c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi sb.append(">"); //$NON-NLS-1$ 409c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi break; 419c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi case '&': 429c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi sb.append("&"); //$NON-NLS-1$ 439c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi break; 449c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi case '\'': 459c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi //http://www.w3.org/TR/xhtml1 469c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi // The named character reference ' (the apostrophe, U+0027) was 479c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi // introduced in XML 1.0 but does not appear in HTML. Authors should 489c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi // therefore use ' instead of ' to work as expected in HTML 4 499c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi // user agents. 509c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi sb.append("'"); //$NON-NLS-1$ 519c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi break; 529c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi case '"': 539c1039484be273dcc63d9ee475655105361b65a6Keisuke Kuroyanagi sb.append("""); //$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