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