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 17 package androidx.core.text; 18 19 import static android.os.Build.VERSION.SDK_INT; 20 21 import android.text.TextUtils; 22 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 import androidx.core.view.ViewCompat; 26 27 import java.util.Locale; 28 29 /** 30 * Backwards compatible version of {@link TextUtils}. 31 */ 32 public 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("<"); //$NON-NLS-1$ 55 break; 56 case '>': 57 sb.append(">"); //$NON-NLS-1$ 58 break; 59 case '&': 60 sb.append("&"); //$NON-NLS-1$ 61 break; 62 case '\'': 63 //http://www.w3.org/TR/xhtml1 64 // The named character reference ' (the apostrophe, U+0027) was 65 // introduced in XML 1.0 but does not appear in HTML. Authors should 66 // therefore use ' instead of ' to work as expected in HTML 4 67 // user agents. 68 sb.append("'"); //$NON-NLS-1$ 69 break; 70 case '"': 71 sb.append("""); //$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