1 /* 2 * Copyright (C) 2022 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.util; 18 19 import android.icu.number.LocalizedNumberFormatter; 20 import android.icu.number.NumberFormatter; 21 import android.icu.text.DateFormat; 22 import android.icu.text.DateTimePatternGenerator; 23 import android.icu.util.MeasureUnit; 24 import android.os.Build; 25 import android.os.Build.VERSION_CODES; 26 27 import androidx.annotation.RequiresApi; 28 import androidx.annotation.RestrictTo; 29 import androidx.annotation.StringDef; 30 31 import org.jspecify.annotations.NonNull; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.Arrays; 36 import java.util.Locale; 37 import java.util.Locale.Category; 38 39 /** 40 * Provides friendly APIs to get the user's locale preferences. The data can refer to 41 * external/cldr/common/main/en.xml. 42 */ 43 @RequiresApi(VERSION_CODES.LOLLIPOP) 44 public final class LocalePreferences { 45 private static final String TAG = LocalePreferences.class.getSimpleName(); 46 47 /** APIs to get the user's preference of the hour cycle. */ 48 public static class HourCycle { 49 private static final String U_EXTENSION_TAG = "hc"; 50 51 /** 12 Hour System (0-11) */ 52 public static final String H11 = "h11"; 53 /** 12 Hour System (1-12) */ 54 public static final String H12 = "h12"; 55 /** 24 Hour System (0-23) */ 56 public static final String H23 = "h23"; 57 /** 24 Hour System (1-24) */ 58 public static final String H24 = "h24"; 59 /** Default hour cycle for the locale */ 60 public static final String DEFAULT = ""; 61 62 @RestrictTo(RestrictTo.Scope.LIBRARY) 63 @StringDef({ 64 H11, 65 H12, 66 H23, 67 H24, 68 DEFAULT 69 }) 70 @Retention(RetentionPolicy.SOURCE) 71 public @interface HourCycleTypes { 72 } 73 HourCycle()74 private HourCycle() { 75 } 76 } 77 78 /** 79 * Return the user's preference of the hour cycle which is from 80 * {@link Locale#getDefault(Locale.Category)}. The returned result is resolved and 81 * bases on the {@code Locale#getDefault(Locale.Category)}. It is one of the strings defined in 82 * {@see HourCycle}, e.g. {@code HourCycle#H11}. 83 */ 84 @HourCycle.HourCycleTypes getHourCycle()85 public static @NonNull String getHourCycle() { 86 return getHourCycle(true); 87 } 88 89 /** 90 * Return the hour cycle setting of the inputted {@link Locale}. The returned result is resolved 91 * and based on the input {@code Locale}. It is one of the strings defined in 92 * {@see HourCycle}, e.g. {@code HourCycle#H11}. 93 */ 94 @HourCycle.HourCycleTypes getHourCycle(@onNull Locale locale)95 public static @NonNull String getHourCycle(@NonNull Locale locale) { 96 return getHourCycle(locale, true); 97 } 98 99 /** 100 * Return the user's preference of the hour cycle which is from 101 * {@link Locale#getDefault(Locale.Category)}, e.g. {@code HourCycle#H11}. 102 * 103 * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains hour cycle subtag, 104 * this argument is ignored. If the 105 * {@code Locale#getDefault(Locale.Category)} doesn't contain hour cycle subtag 106 * and the resolved argument is true, this function tries to find the default 107 * hour cycle for the {@code Locale#getDefault(Locale.Category)}. If the 108 * {@code Locale#getDefault(Locale.Category)} doesn't contain hour cycle subtag 109 * and the resolved argument is false, this function returns empty string 110 * , i.e. {@code HourCycle#DEFAULT}. 111 * @return {@link HourCycle.HourCycleTypes} If the malformed hour cycle format was specified 112 * in the hour cycle subtag, e.g. en-US-u-hc-h32, this function returns empty string, i.e. 113 * {@code HourCycle#DEFAULT}. 114 */ 115 @HourCycle.HourCycleTypes getHourCycle( boolean resolved)116 public static @NonNull String getHourCycle( 117 boolean resolved) { 118 Locale defaultLocale = (Build.VERSION.SDK_INT >= VERSION_CODES.N) 119 ? Api24Impl.getDefaultLocale() 120 : getDefaultLocale(); 121 return getHourCycle(defaultLocale, resolved); 122 } 123 124 /** 125 * Return the hour cycle setting of the inputted {@link Locale}. E.g. "en-US-u-hc-h23". 126 * 127 * @param locale The {@code Locale} to get the hour cycle. 128 * @param resolved If the given {@code Locale} contains hour cycle subtag, this argument is 129 * ignored. If the given {@code Locale} doesn't contain hour cycle subtag and 130 * the resolved argument is true, this function tries to find the default 131 * hour cycle for the given {@code Locale}. If the given {@code Locale} doesn't 132 * contain hour cycle subtag and the resolved argument is false, this function 133 * return empty string, i.e. {@code HourCycle#DEFAULT}. 134 * @return {@link HourCycle.HourCycleTypes} If the malformed hour cycle format was specified 135 * in the hour cycle subtag, e.g. en-US-u-hc-h32, this function returns empty string, i.e. 136 * {@code HourCycle#DEFAULT}. 137 */ 138 @HourCycle.HourCycleTypes getHourCycle(@onNull Locale locale, boolean resolved)139 public static @NonNull String getHourCycle(@NonNull Locale locale, boolean resolved) { 140 String result = getUnicodeLocaleType(HourCycle.U_EXTENSION_TAG, 141 HourCycle.DEFAULT, locale, resolved); 142 if (result != null) { 143 return result; 144 } 145 if (Build.VERSION.SDK_INT >= 33) { 146 return Api33Impl.getHourCycle(locale); 147 } else { 148 return getBaseHourCycle(locale); 149 } 150 } 151 152 /** APIs to get the user's preference of Calendar. */ 153 public static class CalendarType { 154 private static final String U_EXTENSION_TAG = "ca"; 155 /** Chinese Calendar */ 156 public static final String CHINESE = "chinese"; 157 /** Dangi Calendar (Korea Calendar) */ 158 public static final String DANGI = "dangi"; 159 /** Gregorian Calendar */ 160 public static final String GREGORIAN = "gregorian"; 161 /** Hebrew Calendar */ 162 public static final String HEBREW = "hebrew"; 163 /** Indian National Calendar */ 164 public static final String INDIAN = "indian"; 165 /** Islamic Calendar */ 166 public static final String ISLAMIC = "islamic"; 167 /** Islamic Calendar (tabular, civil epoch) */ 168 public static final String ISLAMIC_CIVIL = "islamic-civil"; 169 /** Islamic Calendar (Saudi Arabia, sighting) */ 170 public static final String ISLAMIC_RGSA = "islamic-rgsa"; 171 /** Islamic Calendar (tabular, astronomical epoch) */ 172 public static final String ISLAMIC_TBLA = "islamic-tbla"; 173 /** Islamic Calendar (Umm al-Qura) */ 174 public static final String ISLAMIC_UMALQURA = "islamic-umalqura"; 175 /** Persian Calendar */ 176 public static final String PERSIAN = "persian"; 177 /** Default calendar for the locale */ 178 public static final String DEFAULT = ""; 179 180 @RestrictTo(RestrictTo.Scope.LIBRARY) 181 @StringDef({ 182 CHINESE, 183 DANGI, 184 GREGORIAN, 185 HEBREW, 186 INDIAN, 187 ISLAMIC, 188 ISLAMIC_CIVIL, 189 ISLAMIC_RGSA, 190 ISLAMIC_TBLA, 191 ISLAMIC_UMALQURA, 192 PERSIAN, 193 DEFAULT 194 }) 195 @Retention(RetentionPolicy.SOURCE) 196 public @interface CalendarTypes { 197 } 198 CalendarType()199 private CalendarType() { 200 } 201 } 202 203 /** 204 * Return the user's preference of the calendar type which is from {@link 205 * Locale#getDefault(Locale.Category)}. The returned result is resolved and bases on 206 * the {@code Locale#getDefault(Locale.Category)} settings. It is one of the strings defined in 207 * {@see CalendarType}, e.g. {@code CalendarType#CHINESE}. 208 */ 209 @CalendarType.CalendarTypes getCalendarType()210 public static @NonNull String getCalendarType() { 211 return getCalendarType(true); 212 } 213 214 /** 215 * Return the calendar type of the inputted {@link Locale}. The returned result is resolved and 216 * based on the input {@link Locale} settings. It is one of the strings defined in 217 * {@see CalendarType}, e.g. {@code CalendarType#CHINESE}. 218 */ 219 @CalendarType.CalendarTypes getCalendarType(@onNull Locale locale)220 public static @NonNull String getCalendarType(@NonNull Locale locale) { 221 return getCalendarType(locale, true); 222 } 223 224 /** 225 * Return the user's preference of the calendar type which is from {@link 226 * Locale#getDefault(Category)}, e.g. {@code CalendarType#CHINESE}. 227 * 228 * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains calendar type 229 * subtag, this argument is ignored. If the 230 * {@code Locale#getDefault(Locale.Category)} doesn't contain calendar type 231 * subtag and the resolved argument is true, this function tries to find 232 * the default calendar type for the 233 * {@code Locale#getDefault(Locale.Category)}. If the 234 * {@code Locale#getDefault(Locale.Category)} doesn't contain calendar type 235 * subtag and the resolved argument is false, this function returns empty string 236 * , i.e. {@code CalendarType#DEFAULT}. 237 * @return {@link CalendarType.CalendarTypes} If the malformed calendar type format was 238 * specified in the calendar type subtag, e.g. en-US-u-ca-calendar, this function returns 239 * empty string, i.e. {@code CalendarType#DEFAULT}. 240 */ 241 @CalendarType.CalendarTypes getCalendarType(boolean resolved)242 public static @NonNull String getCalendarType(boolean resolved) { 243 Locale defaultLocale = (Build.VERSION.SDK_INT >= VERSION_CODES.N) 244 ? Api24Impl.getDefaultLocale() 245 : getDefaultLocale(); 246 return getCalendarType(defaultLocale, resolved); 247 } 248 249 /** 250 * Return the calendar type of the inputted {@link Locale}, e.g. {@code CalendarType#CHINESE}. 251 * 252 * @param locale The {@link Locale} to get the calendar type. 253 * @param resolved If the given {@code Locale} contains calendar type subtag, this argument is 254 * ignored. If the given {@code Locale} doesn't contain calendar type subtag and 255 * the resolved argument is true, this function tries to find the default 256 * calendar type for the given {@code Locale}. If the given {@code Locale} 257 * doesn't contain calendar type subtag and the resolved argument is false, this 258 * function return empty string, i.e. {@code CalendarType#DEFAULT}. 259 * @return {@link CalendarType.CalendarTypes} If the malformed calendar type format was 260 * specified in the calendar type subtag, e.g. en-US-u-ca-calendar, this function returns 261 * empty string, i.e. {@code CalendarType#DEFAULT}. 262 */ 263 @CalendarType.CalendarTypes getCalendarType(@onNull Locale locale, boolean resolved)264 public static @NonNull String getCalendarType(@NonNull Locale locale, boolean resolved) { 265 String result = getUnicodeLocaleType(CalendarType.U_EXTENSION_TAG, 266 CalendarType.DEFAULT, locale, resolved); 267 if (result != null) { 268 return result; 269 } 270 if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { 271 return Api24Impl.getCalendarType(locale); 272 } else { 273 return resolved ? CalendarType.GREGORIAN : CalendarType.DEFAULT; 274 } 275 } 276 277 /** APIs to get the user's preference of temperature unit. */ 278 public static class TemperatureUnit { 279 private static final String U_EXTENSION_TAG = "mu"; 280 /** Celsius */ 281 public static final String CELSIUS = "celsius"; 282 /** Fahrenheit */ 283 public static final String FAHRENHEIT = "fahrenhe"; 284 /** Kelvin */ 285 public static final String KELVIN = "kelvin"; 286 /** Default Temperature for the locale */ 287 public static final String DEFAULT = ""; 288 289 @RestrictTo(RestrictTo.Scope.LIBRARY) 290 @StringDef({ 291 CELSIUS, 292 FAHRENHEIT, 293 KELVIN, 294 DEFAULT 295 }) 296 @Retention(RetentionPolicy.SOURCE) 297 public @interface TemperatureUnits { 298 } 299 TemperatureUnit()300 private TemperatureUnit() { 301 } 302 } 303 304 /** 305 * Return the user's preference of the temperature unit which is from {@link 306 * Locale#getDefault(Locale.Category)}. The returned result is resolved and bases on the 307 * {@code Locale#getDefault(Locale.Category)} settings. It is one of the strings defined in 308 * {@see TemperatureUnit}, e.g. {@code TemperatureUnit#FAHRENHEIT}. 309 */ 310 @TemperatureUnit.TemperatureUnits getTemperatureUnit()311 public static @NonNull String getTemperatureUnit() { 312 return getTemperatureUnit(true); 313 } 314 315 /** 316 * Return the temperature unit of the inputted {@link Locale}. It is one of the strings 317 * defined in {@see TemperatureUnit}, e.g. {@code TemperatureUnit#FAHRENHEIT}. 318 */ 319 @TemperatureUnit.TemperatureUnits getTemperatureUnit( @onNull Locale locale)320 public static @NonNull String getTemperatureUnit( 321 @NonNull Locale locale) { 322 return getTemperatureUnit(locale, true); 323 } 324 325 /** 326 * Return the user's preference of the temperature unit which is from {@link 327 * Locale#getDefault(Locale.Category)}, e.g. {@code TemperatureUnit#FAHRENHEIT}. 328 * 329 * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains temperature unit 330 * subtag, this argument is ignored. If the 331 * {@code Locale#getDefault(Locale.Category)} doesn't contain temperature unit 332 * subtag and the resolved argument is true, this function tries to find 333 * the default temperature unit for the 334 * {@code Locale#getDefault(Locale.Category)}. If the 335 * {@code Locale#getDefault(Locale.Category)} doesn't contain temperature unit 336 * subtag and the resolved argument is false, this function returns empty string 337 * , i.e. {@code TemperatureUnit#DEFAULT}. 338 * @return {@link TemperatureUnit.TemperatureUnits} If the malformed temperature unit format was 339 * specified in the temperature unit subtag, e.g. en-US-u-mu-temperature, this function returns 340 * empty string, i.e. {@code TemperatureUnit#DEFAULT}. 341 */ 342 @TemperatureUnit.TemperatureUnits getTemperatureUnit(boolean resolved)343 public static @NonNull String getTemperatureUnit(boolean resolved) { 344 Locale defaultLocale = (Build.VERSION.SDK_INT >= VERSION_CODES.N) 345 ? Api24Impl.getDefaultLocale() 346 : getDefaultLocale(); 347 return getTemperatureUnit(defaultLocale, resolved); 348 } 349 350 /** 351 * Return the temperature unit of the inputted {@link Locale}. E.g. "fahrenheit" 352 * 353 * @param locale The {@link Locale} to get the temperature unit. 354 * @param resolved If the given {@code Locale} contains temperature unit subtag, this argument 355 * is ignored. If the given {@code Locale} doesn't contain temperature unit 356 * subtag and the resolved argument is true, this function tries to find 357 * the default temperature unit for the given {@code Locale}. If the given 358 * {@code Locale} doesn't contain temperature unit subtag and the resolved 359 * argument is false, this function return empty string, i.e. 360 * {@code TemperatureUnit#DEFAULT}. 361 * @return {@link TemperatureUnit.TemperatureUnits} If the malformed temperature unit format was 362 * specified in the temperature unit subtag, e.g. en-US-u-mu-temperature, this function returns 363 * empty string, i.e. {@code TemperatureUnit#DEFAULT}. 364 */ 365 @TemperatureUnit.TemperatureUnits getTemperatureUnit(@onNull Locale locale, boolean resolved)366 public static @NonNull String getTemperatureUnit(@NonNull Locale locale, boolean resolved) { 367 String result = getUnicodeLocaleType(TemperatureUnit.U_EXTENSION_TAG, 368 TemperatureUnit.DEFAULT, locale, resolved); 369 if (result != null) { 370 return result; 371 } 372 if (Build.VERSION.SDK_INT >= 33) { 373 return Api33Impl.getResolvedTemperatureUnit(locale); 374 } else { 375 return getTemperatureHardCoded(locale); 376 } 377 } 378 379 /** APIs to get the user's preference of the first day of week. */ 380 public static class FirstDayOfWeek { 381 private static final String U_EXTENSION_TAG = "fw"; 382 /** Sunday */ 383 public static final String SUNDAY = "sun"; 384 /** Monday */ 385 public static final String MONDAY = "mon"; 386 /** Tuesday */ 387 public static final String TUESDAY = "tue"; 388 /** Wednesday */ 389 public static final String WEDNESDAY = "wed"; 390 /** Thursday */ 391 public static final String THURSDAY = "thu"; 392 /** Friday */ 393 public static final String FRIDAY = "fri"; 394 /** Saturday */ 395 public static final String SATURDAY = "sat"; 396 /** Default first day of week for the locale */ 397 public static final String DEFAULT = ""; 398 399 @RestrictTo(RestrictTo.Scope.LIBRARY) 400 @StringDef({ 401 SUNDAY, 402 MONDAY, 403 TUESDAY, 404 WEDNESDAY, 405 THURSDAY, 406 FRIDAY, 407 SATURDAY, 408 DEFAULT 409 }) 410 @Retention(RetentionPolicy.SOURCE) 411 public @interface Days { 412 } 413 FirstDayOfWeek()414 private FirstDayOfWeek() { 415 } 416 } 417 418 /** 419 * Return the user's preference of the first day of week which is from 420 * {@link Locale#getDefault(Locale.Category)}. The returned result is resolved and bases on the 421 * {@code Locale#getDefault(Locale.Category)} settings. It is one of the strings defined in 422 * {@see FirstDayOfWeek}, e.g. {@code FirstDayOfWeek#SUNDAY}. 423 */ 424 @FirstDayOfWeek.Days getFirstDayOfWeek()425 public static @NonNull String getFirstDayOfWeek() { 426 return getFirstDayOfWeek(true); 427 } 428 429 /** 430 * Return the first day of week of the inputted {@link Locale}. The returned result is resolved 431 * and based on the input {@code Locale} settings. It is one of the strings defined in 432 * {@see FirstDayOfWeek}, e.g. {@code FirstDayOfWeek#SUNDAY}. 433 */ 434 @FirstDayOfWeek.Days getFirstDayOfWeek(@onNull Locale locale)435 public static @NonNull String getFirstDayOfWeek(@NonNull Locale locale) { 436 return getFirstDayOfWeek(locale, true); 437 } 438 439 /** 440 * Return the user's preference of the first day of week which is from {@link 441 * Locale#getDefault(Locale.Category)}, e.g. {@code FirstDayOfWeek#SUNDAY}. 442 * 443 * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains first day of week 444 * subtag, this argument is ignored. If the 445 * {@code Locale#getDefault(Locale.Category)} doesn't contain first day of week 446 * subtag and the resolved argument is true, this function tries to find 447 * the default first day of week for the 448 * {@code Locale#getDefault(Locale.Category)}. If the 449 * {@code Locale#getDefault(Locale.Category)} doesn't contain first day of week 450 * subtag and the resolved argument is false, this function returns empty string 451 * , i.e. {@code FirstDayOfWeek#DEFAULT}. 452 * @return {@link FirstDayOfWeek.Days} If the malformed first day of week format was specified 453 * in the first day of week subtag, e.g. en-US-u-fw-days, this function returns empty string, 454 * i.e. {@code FirstDayOfWeek#DEFAULT}. 455 */ 456 @FirstDayOfWeek.Days getFirstDayOfWeek(boolean resolved)457 public static @NonNull String getFirstDayOfWeek(boolean resolved) { 458 Locale defaultLocale = (Build.VERSION.SDK_INT >= VERSION_CODES.N) 459 ? Api24Impl.getDefaultLocale() 460 : getDefaultLocale(); 461 return getFirstDayOfWeek(defaultLocale, resolved); 462 } 463 464 /** 465 * Return the first day of week of the inputted {@link Locale}, 466 * e.g. {@code FirstDayOfWeek#SUNDAY}. 467 * 468 * @param locale The {@link Locale} to get the first day of week. 469 * @param resolved If the given {@code Locale} contains first day of week subtag, this argument 470 * is ignored. If the given {@code Locale} doesn't contain first day of week 471 * subtag and the resolved argument is true, this function tries to find 472 * the default first day of week for the given {@code Locale}. If the given 473 * {@code Locale} doesn't contain first day of week subtag and the resolved 474 * argument is false, this function return empty string, i.e. 475 * {@code FirstDayOfWeek#DEFAULT}. 476 * @return {@link FirstDayOfWeek.Days} If the malformed first day of week format was 477 * specified in the first day of week subtag, e.g. en-US-u-fw-days, this function returns 478 * empty string, i.e. {@code FirstDayOfWeek#DEFAULT}. 479 */ 480 @FirstDayOfWeek.Days getFirstDayOfWeek( @onNull Locale locale, boolean resolved)481 public static @NonNull String getFirstDayOfWeek( 482 @NonNull Locale locale, boolean resolved) { 483 String result = getUnicodeLocaleType(FirstDayOfWeek.U_EXTENSION_TAG, 484 FirstDayOfWeek.DEFAULT, locale, resolved); 485 return result != null ? result : getBaseFirstDayOfWeek(locale); 486 } 487 getUnicodeLocaleType(String tag, String defaultValue, Locale locale, boolean resolved)488 private static String getUnicodeLocaleType(String tag, String defaultValue, Locale locale, 489 boolean resolved) { 490 String ext = locale.getUnicodeLocaleType(tag); 491 if (ext != null) { 492 return ext; 493 } 494 if (!resolved) { 495 return defaultValue; 496 } 497 return null; 498 } 499 500 501 // Warning: This list of country IDs must be in alphabetical order for binarySearch to 502 // work correctly. 503 private static final String[] WEATHER_FAHRENHEIT_COUNTRIES = 504 {"BS", "BZ", "KY", "PR", "PW", "US"}; 505 506 @TemperatureUnit.TemperatureUnits getTemperatureHardCoded(Locale locale)507 private static String getTemperatureHardCoded(Locale locale) { 508 return Arrays.binarySearch(WEATHER_FAHRENHEIT_COUNTRIES, locale.getCountry()) >= 0 509 ? TemperatureUnit.FAHRENHEIT 510 : TemperatureUnit.CELSIUS; 511 } 512 513 @HourCycle.HourCycleTypes getBaseHourCycle(@onNull Locale locale)514 private static String getBaseHourCycle(@NonNull Locale locale) { 515 String pattern = 516 android.text.format.DateFormat.getBestDateTimePattern( 517 locale, "jm"); 518 return pattern.contains("H") ? HourCycle.H23 : HourCycle.H12; 519 } 520 521 @FirstDayOfWeek.Days getBaseFirstDayOfWeek(@onNull Locale locale)522 private static String getBaseFirstDayOfWeek(@NonNull Locale locale) { 523 // A known bug affects both the {@code android.icu.util.Calendar} and 524 // {@code java.util.Calendar}: they ignore the "fw" field in the -u- extension, even if 525 // present. So please do not remove the explicit check on getUnicodeLocaleType, 526 // which protects us from that bug. 527 return getStringOfFirstDayOfWeek( 528 java.util.Calendar.getInstance(locale).getFirstDayOfWeek()); 529 } 530 getStringOfFirstDayOfWeek(int fw)531 private static String getStringOfFirstDayOfWeek(int fw) { 532 String[] arrDays = { 533 FirstDayOfWeek.SUNDAY, 534 FirstDayOfWeek.MONDAY, 535 FirstDayOfWeek.TUESDAY, 536 FirstDayOfWeek.WEDNESDAY, 537 FirstDayOfWeek.THURSDAY, 538 FirstDayOfWeek.FRIDAY, 539 FirstDayOfWeek.SATURDAY}; 540 return fw >= 1 && fw <= 7 ? arrDays[fw - 1] : FirstDayOfWeek.DEFAULT; 541 } 542 getDefaultLocale()543 private static Locale getDefaultLocale() { 544 return Locale.getDefault(); 545 } 546 547 @RequiresApi(VERSION_CODES.N) 548 private static class Api24Impl { 549 @CalendarType.CalendarTypes getCalendarType(@onNull Locale locale)550 static String getCalendarType(@NonNull Locale locale) { 551 return android.icu.util.Calendar.getInstance(locale).getType(); 552 } 553 getDefaultLocale()554 static Locale getDefaultLocale() { 555 return Locale.getDefault(Category.FORMAT); 556 } 557 Api24Impl()558 private Api24Impl() { 559 } 560 } 561 562 @RequiresApi(VERSION_CODES.TIRAMISU) 563 private static class Api33Impl { 564 @TemperatureUnit.TemperatureUnits getResolvedTemperatureUnit(@onNull Locale locale)565 static String getResolvedTemperatureUnit(@NonNull Locale locale) { 566 LocalizedNumberFormatter nf = NumberFormatter.with() 567 .usage("weather") 568 .unit(MeasureUnit.CELSIUS) 569 .locale(locale); 570 String unit = nf.format(1).getOutputUnit().getIdentifier(); 571 if (unit.startsWith(TemperatureUnit.FAHRENHEIT)) { 572 return TemperatureUnit.FAHRENHEIT; 573 } 574 return unit; 575 } 576 577 @HourCycle.HourCycleTypes getHourCycle(@onNull Locale locale)578 static String getHourCycle(@NonNull Locale locale) { 579 return getHourCycleType( 580 DateTimePatternGenerator.getInstance(locale).getDefaultHourCycle()); 581 } 582 583 @HourCycle.HourCycleTypes getHourCycleType( DateFormat.HourCycle hourCycle)584 private static String getHourCycleType( 585 DateFormat.HourCycle hourCycle) { 586 switch (hourCycle) { 587 case HOUR_CYCLE_11: 588 return HourCycle.H11; 589 case HOUR_CYCLE_12: 590 return HourCycle.H12; 591 case HOUR_CYCLE_23: 592 return HourCycle.H23; 593 case HOUR_CYCLE_24: 594 return HourCycle.H24; 595 default: 596 return HourCycle.DEFAULT; 597 } 598 } 599 Api33Impl()600 private Api33Impl() { 601 } 602 } 603 LocalePreferences()604 private LocalePreferences() { 605 } 606 } 607