1 /* 2 * Copyright (C) 2017 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.os; 18 19 import android.os.Build; 20 import android.os.LocaleList; 21 22 import androidx.annotation.IntRange; 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 import androidx.annotation.RequiresApi; 26 import androidx.annotation.Size; 27 28 import java.util.Locale; 29 30 /** 31 * Helper for accessing features in {@link LocaleList}. 32 */ 33 public final class LocaleListCompat { 34 static final LocaleListInterface IMPL; 35 private static final LocaleListCompat sEmptyLocaleList = new LocaleListCompat(); 36 37 38 static class LocaleListCompatBaseImpl implements LocaleListInterface { 39 private LocaleListHelper mLocaleList = new LocaleListHelper(); 40 41 @Override setLocaleList(@onNull Locale... list)42 public void setLocaleList(@NonNull Locale... list) { 43 mLocaleList = new LocaleListHelper(list); 44 } 45 46 @Override getLocaleList()47 public Object getLocaleList() { 48 return mLocaleList; 49 } 50 51 @Override get(int index)52 public Locale get(int index) { 53 return mLocaleList.get(index); 54 } 55 56 @Override isEmpty()57 public boolean isEmpty() { 58 return mLocaleList.isEmpty(); 59 } 60 61 @Override 62 @IntRange(from = 0) size()63 public int size() { 64 return mLocaleList.size(); 65 } 66 67 @Override 68 @IntRange(from = -1) indexOf(Locale locale)69 public int indexOf(Locale locale) { 70 return mLocaleList.indexOf(locale); 71 } 72 73 @Override equals(Object other)74 public boolean equals(Object other) { 75 return mLocaleList.equals(((LocaleListCompat) other).unwrap()); 76 } 77 78 @Override hashCode()79 public int hashCode() { 80 return mLocaleList.hashCode(); 81 } 82 83 @Override toString()84 public String toString() { 85 return mLocaleList.toString(); 86 } 87 88 @Override toLanguageTags()89 public String toLanguageTags() { 90 return mLocaleList.toLanguageTags(); 91 } 92 93 @Nullable 94 @Override getFirstMatch(String[] supportedLocales)95 public Locale getFirstMatch(String[] supportedLocales) { 96 if (mLocaleList != null) { 97 return mLocaleList.getFirstMatch(supportedLocales); 98 } 99 return null; 100 } 101 } 102 103 @RequiresApi(24) 104 static class LocaleListCompatApi24Impl implements LocaleListInterface { 105 private LocaleList mLocaleList = new LocaleList(); 106 107 @Override setLocaleList(@onNull Locale... list)108 public void setLocaleList(@NonNull Locale... list) { 109 mLocaleList = new LocaleList(list); 110 } 111 112 @Override getLocaleList()113 public Object getLocaleList() { 114 return mLocaleList; 115 } 116 117 @Override get(int index)118 public Locale get(int index) { 119 return mLocaleList.get(index); 120 } 121 122 @Override isEmpty()123 public boolean isEmpty() { 124 return mLocaleList.isEmpty(); 125 } 126 127 @Override 128 @IntRange(from = 0) size()129 public int size() { 130 return mLocaleList.size(); 131 } 132 133 @Override 134 @IntRange(from = -1) indexOf(Locale locale)135 public int indexOf(Locale locale) { 136 return mLocaleList.indexOf(locale); 137 } 138 139 @Override equals(Object other)140 public boolean equals(Object other) { 141 return mLocaleList.equals(((LocaleListCompat) other).unwrap()); 142 } 143 144 @Override hashCode()145 public int hashCode() { 146 return mLocaleList.hashCode(); 147 } 148 149 @Override toString()150 public String toString() { 151 return mLocaleList.toString(); 152 } 153 154 @Override toLanguageTags()155 public String toLanguageTags() { 156 return mLocaleList.toLanguageTags(); 157 } 158 159 @Nullable 160 @Override getFirstMatch(String[] supportedLocales)161 public Locale getFirstMatch(String[] supportedLocales) { 162 if (mLocaleList != null) { 163 return mLocaleList.getFirstMatch(supportedLocales); 164 } 165 return null; 166 } 167 } 168 169 static { 170 if (Build.VERSION.SDK_INT >= 24) { 171 IMPL = new LocaleListCompatApi24Impl(); 172 } else { 173 IMPL = new LocaleListCompatBaseImpl(); 174 } 175 } 176 LocaleListCompat()177 private LocaleListCompat() {} 178 179 /** 180 * Creates a new instance of {@link LocaleListCompat} from the Locale list. 181 */ 182 @RequiresApi(24) wrap(Object object)183 public static LocaleListCompat wrap(Object object) { 184 LocaleListCompat instance = new LocaleListCompat(); 185 if (object instanceof LocaleList) { 186 instance.setLocaleList((LocaleList) object); 187 188 } 189 return instance; 190 } 191 192 /** 193 * Gets the underlying framework object. 194 * 195 * @return an android.os.LocaleList object if API >= 24 , or {@link Locale} if not. 196 */ 197 @Nullable unwrap()198 public Object unwrap() { 199 return IMPL.getLocaleList(); 200 } 201 202 /** 203 * Creates a new instance of {@link LocaleListCompat} from the {@link Locale} array. 204 */ create(@onNull Locale... localeList)205 public static LocaleListCompat create(@NonNull Locale... localeList) { 206 LocaleListCompat instance = new LocaleListCompat(); 207 instance.setLocaleListArray(localeList); 208 return instance; 209 } 210 211 /** 212 * Retrieves the {@link Locale} at the specified index. 213 * 214 * @param index The position to retrieve. 215 * @return The {@link Locale} in the given index 216 */ get(int index)217 public Locale get(int index) { 218 return IMPL.get(index); 219 } 220 221 /** 222 * Returns whether the {@link LocaleListCompat} contains no {@link Locale} items. 223 * 224 * @return {@code true} if this {@link LocaleListCompat} has no {@link Locale} items, 225 * {@code false} otherwise 226 */ isEmpty()227 public boolean isEmpty() { 228 return IMPL.isEmpty(); 229 } 230 231 /** 232 * Returns the number of {@link Locale} items in this {@link LocaleListCompat}. 233 */ 234 @IntRange(from = 0) size()235 public int size() { 236 return IMPL.size(); 237 } 238 239 /** 240 * Searches this {@link LocaleListCompat} for the specified {@link Locale} and returns the 241 * index of the first occurrence. 242 * 243 * @param locale The {@link Locale} to search for. 244 * @return The index of the first occurrence of the {@link Locale} or {@code -1} if the item 245 * wasn't found 246 */ 247 @IntRange(from = -1) indexOf(Locale locale)248 public int indexOf(Locale locale) { 249 return IMPL.indexOf(locale); 250 } 251 252 /** 253 * Retrieves a String representation of the language tags in this list. 254 */ 255 @NonNull toLanguageTags()256 public String toLanguageTags() { 257 return IMPL.toLanguageTags(); 258 } 259 260 /** 261 * Returns the first match in the locale list given an unordered array of supported locales 262 * in BCP 47 format. 263 * 264 * @return The first {@link Locale} from this list that appears in the given array, or 265 * {@code null} if the {@link LocaleListCompat} is empty. 266 */ getFirstMatch(String[] supportedLocales)267 public Locale getFirstMatch(String[] supportedLocales) { 268 return IMPL.getFirstMatch(supportedLocales); 269 } 270 271 /** 272 * Retrieve an empty instance of {@link LocaleList}. 273 */ 274 @NonNull getEmptyLocaleList()275 public static LocaleListCompat getEmptyLocaleList() { 276 return sEmptyLocaleList; 277 } 278 279 /** 280 * Generates a new LocaleList with the given language tags. 281 * 282 * <p>Note that for API < 24 only the first language tag will be used.</> 283 * 284 * @param list The language tags to be included as a single {@link String} separated by commas. 285 * @return A new instance with the {@link Locale} items identified by the given tags. 286 */ 287 @NonNull forLanguageTags(@ullable String list)288 public static LocaleListCompat forLanguageTags(@Nullable String list) { 289 if (list == null || list.isEmpty()) { 290 return getEmptyLocaleList(); 291 } else { 292 final String[] tags = list.split(",", -1); 293 final Locale[] localeArray = new Locale[tags.length]; 294 for (int i = 0; i < localeArray.length; i++) { 295 localeArray[i] = Build.VERSION.SDK_INT >= 21 296 ? Locale.forLanguageTag(tags[i]) 297 : LocaleHelper.forLanguageTag(tags[i]); 298 } 299 LocaleListCompat instance = new LocaleListCompat(); 300 instance.setLocaleListArray(localeArray); 301 return instance; 302 } 303 } 304 305 /** 306 * Returns the default locale list, adjusted by moving the default locale to its first 307 * position. 308 */ 309 @NonNull @Size(min = 1) getAdjustedDefault()310 public static LocaleListCompat getAdjustedDefault() { 311 if (Build.VERSION.SDK_INT >= 24) { 312 return LocaleListCompat.wrap(LocaleList.getAdjustedDefault()); 313 } else { 314 return LocaleListCompat.create(Locale.getDefault()); 315 } 316 } 317 318 /** 319 * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but 320 * not necessarily at the top of the list. The default locale not being at the top of the list 321 * is an indication that the system has set the default locale to one of the user's other 322 * preferred locales, having concluded that the primary preference is not supported but a 323 * secondary preference is. 324 * 325 * <p>Note that for API >= 24 the default LocaleList would change if Locale.setDefault() is 326 * called. This method takes that into account by always checking the output of 327 * Locale.getDefault() and recalculating the default LocaleList if needed.</p> 328 */ 329 @NonNull @Size(min = 1) getDefault()330 public static LocaleListCompat getDefault() { 331 if (Build.VERSION.SDK_INT >= 24) { 332 return LocaleListCompat.wrap(LocaleList.getDefault()); 333 } else { 334 return LocaleListCompat.create(Locale.getDefault()); 335 } 336 } 337 338 @Override equals(Object other)339 public boolean equals(Object other) { 340 return IMPL.equals(other); 341 } 342 343 @Override hashCode()344 public int hashCode() { 345 return IMPL.hashCode(); 346 } 347 348 @Override toString()349 public String toString() { 350 return IMPL.toString(); 351 } 352 353 @RequiresApi(24) setLocaleList(LocaleList localeList)354 private void setLocaleList(LocaleList localeList) { 355 final int localeListSize = localeList.size(); 356 if (localeListSize > 0) { 357 Locale[] localeArrayList = new Locale[localeListSize]; 358 for (int i = 0; i < localeListSize; i++) { 359 localeArrayList[i] = localeList.get(i); 360 } 361 IMPL.setLocaleList(localeArrayList); 362 } 363 } 364 setLocaleListArray(Locale... localeArrayList)365 private void setLocaleListArray(Locale... localeArrayList) { 366 IMPL.setLocaleList(localeArrayList); 367 } 368 } 369