1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2004-2014, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package android.icu.util; 11 12 import java.text.ParseException; 13 import java.util.ArrayList; 14 import java.util.Arrays; 15 import java.util.BitSet; 16 import java.util.Date; 17 import java.util.HashMap; 18 import java.util.List; 19 import java.util.Map; 20 import java.util.MissingResourceException; 21 import java.util.ResourceBundle; 22 23 import android.icu.impl.Utility; 24 import android.icu.text.BreakIterator; 25 import android.icu.text.Collator; 26 import android.icu.text.DateFormat; 27 import android.icu.text.NumberFormat; 28 import android.icu.text.SimpleDateFormat; 29 30 /** 31 * This convenience class provides a mechanism for bundling together different 32 * globalization preferences. It includes: 33 * <ul> 34 * <li>A list of locales/languages in preference order</li> 35 * <li>A territory</li> 36 * <li>A currency</li> 37 * <li>A timezone</li> 38 * <li>A calendar</li> 39 * <li>A collator (for language-sensitive sorting, searching, and matching).</li> 40 * <li>Explicit overrides for date/time formats, etc.</li> 41 * </ul> 42 * The class will heuristically compute implicit, heuristic values for the above 43 * based on available data if explicit values are not supplied. These implicit 44 * values can be presented to users for confirmation, or replacement if the 45 * values are incorrect. 46 * <p> 47 * To reset any explicit field so that it will get heuristic values, pass in 48 * null. For example, myPreferences.setLocale(null); 49 * <p> 50 * All of the heuristics can be customized by subclasses, by overriding 51 * getTerritory(), guessCollator(), etc. 52 * <p> 53 * The class also supplies display names for languages, scripts, territories, 54 * currencies, timezones, etc. These are computed according to the 55 * locale/language preference list. Thus, if the preference is Breton; French; 56 * English, then the display name for a language will be returned in Breton if 57 * available, otherwise in French if available, otherwise in English. 58 * <p> 59 * The codes used to reference territory, currency, etc. are as defined elsewhere 60 * in ICU, and are taken from CLDR (which reflects RFC 3066bis usage, ISO 4217, 61 * and the TZ Timezone database identifiers). 62 * <p> 63 * <b>This is at a prototype stage, and has not incorporated all the design 64 * changes that we would like yet; further feedback is welcome.</b></p> 65 * Note: 66 * <ul> 67 * <li>to get the display name for the first day of the week, use the calendar + 68 * display names.</li> 69 * <li>to get the work days, ask the calendar (when that is available).</li> 70 * <li>to get papersize / measurement system/bidi-orientation, ask the locale 71 * (when that is available there)</li> 72 * <li>to get the field order in a date, and whether a time is 24hour or not, 73 * ask the DateFormat (when that is available there)</li> 74 * <li>it will support HOST locale when it becomes available (it is a special 75 * locale that will ask the services to use the host platform's values).</li> 76 * </ul> 77 * 78 * @hide Only a subset of ICU is exposed in Android 79 * @hide draft / provisional / internal are hidden on Android 80 */ 81 82 //TODO: 83 // - Add Holidays 84 // - Add convenience to get/take Locale as well as ULocale. 85 // - Add Lenient datetime formatting when that is available. 86 // - Should this be serializable? 87 // - Other utilities? 88 89 public class GlobalizationPreferences implements Freezable<GlobalizationPreferences> { 90 91 /** 92 * Default constructor 93 * @hide draft / provisional / internal are hidden on Android 94 */ GlobalizationPreferences()95 public GlobalizationPreferences(){} 96 /** 97 * Number Format type 98 * @hide draft / provisional / internal are hidden on Android 99 */ 100 public static final int 101 NF_NUMBER = 0, // NumberFormat.NUMBERSTYLE 102 NF_CURRENCY = 1, // NumberFormat.CURRENCYSTYLE 103 NF_PERCENT = 2, // NumberFormat.PERCENTSTYLE 104 NF_SCIENTIFIC = 3, // NumberFormat.SCIENTIFICSTYLE 105 NF_INTEGER = 4; // NumberFormat.INTEGERSTYLE 106 107 private static final int NF_LIMIT = NF_INTEGER + 1; 108 109 /** 110 * Date Format type 111 * @hide draft / provisional / internal are hidden on Android 112 */ 113 public static final int 114 DF_FULL = DateFormat.FULL, // 0 115 DF_LONG = DateFormat.LONG, // 1 116 DF_MEDIUM = DateFormat.MEDIUM, // 2 117 DF_SHORT = DateFormat.SHORT, // 3 118 DF_NONE = 4; 119 120 private static final int DF_LIMIT = DF_NONE + 1; 121 122 /** 123 * For selecting a choice of display names 124 * @hide draft / provisional / internal are hidden on Android 125 */ 126 public static final int 127 ID_LOCALE = 0, 128 ID_LANGUAGE = 1, 129 ID_SCRIPT = 2, 130 ID_TERRITORY = 3, 131 ID_VARIANT = 4, 132 ID_KEYWORD = 5, 133 ID_KEYWORD_VALUE = 6, 134 ID_CURRENCY = 7, 135 ID_CURRENCY_SYMBOL = 8, 136 ID_TIMEZONE = 9; 137 138 //private static final int ID_LIMIT = ID_TIMEZONE + 1; 139 140 /** 141 * Break iterator type 142 * @hide draft / provisional / internal are hidden on Android 143 */ 144 public static final int 145 BI_CHARACTER = BreakIterator.KIND_CHARACTER, // 0 146 BI_WORD = BreakIterator.KIND_WORD, // 1 147 BI_LINE = BreakIterator.KIND_LINE, // 2 148 BI_SENTENCE = BreakIterator.KIND_SENTENCE, // 3 149 BI_TITLE = BreakIterator.KIND_TITLE; // 4 150 151 private static final int BI_LIMIT = BI_TITLE + 1; 152 153 /** 154 * Sets the language/locale priority list. If other information is 155 * not (yet) available, this is used to to produce a default value 156 * for the appropriate territory, currency, timezone, etc. The 157 * user should be given the opportunity to correct those defaults 158 * in case they are incorrect. 159 * 160 * @param inputLocales list of locales in priority order, eg {"be", "fr"} 161 * for Breton first, then French if that fails. 162 * @return this, for chaining 163 * @hide draft / provisional / internal are hidden on Android 164 */ setLocales(List<ULocale> inputLocales)165 public GlobalizationPreferences setLocales(List<ULocale> inputLocales) { 166 if (isFrozen()) { 167 throw new UnsupportedOperationException("Attempt to modify immutable object"); 168 } 169 locales = processLocales(inputLocales); 170 return this; 171 } 172 173 /** 174 * Get a copy of the language/locale priority list 175 * 176 * @return a copy of the language/locale priority list. 177 * @hide draft / provisional / internal are hidden on Android 178 */ getLocales()179 public List<ULocale> getLocales() { 180 List<ULocale> result; 181 if (locales == null) { 182 result = guessLocales(); 183 } else { 184 result = new ArrayList<ULocale>(); 185 result.addAll(locales); 186 } 187 return result; 188 } 189 190 /** 191 * Convenience function for getting the locales in priority order 192 * @param index The index (0..n) of the desired item. 193 * @return desired item. null if index is out of range 194 * @hide draft / provisional / internal are hidden on Android 195 */ getLocale(int index)196 public ULocale getLocale(int index) { 197 List<ULocale> lcls = locales; 198 if (lcls == null) { 199 lcls = guessLocales(); 200 } 201 if (index >= 0 && index < lcls.size()) { 202 return lcls.get(index); 203 } 204 return null; 205 } 206 207 /** 208 * Convenience routine for setting the language/locale priority 209 * list from an array. 210 * 211 * @see #setLocales(List locales) 212 * @param uLocales list of locales in an array 213 * @return this, for chaining 214 * @hide draft / provisional / internal are hidden on Android 215 */ setLocales(ULocale[] uLocales)216 public GlobalizationPreferences setLocales(ULocale[] uLocales) { 217 if (isFrozen()) { 218 throw new UnsupportedOperationException("Attempt to modify immutable object"); 219 } 220 return setLocales(Arrays.asList(uLocales)); 221 } 222 223 /** 224 * Convenience routine for setting the language/locale priority 225 * list from a single locale/language. 226 * 227 * @see #setLocales(List locales) 228 * @param uLocale single locale 229 * @return this, for chaining 230 * @hide draft / provisional / internal are hidden on Android 231 */ setLocale(ULocale uLocale)232 public GlobalizationPreferences setLocale(ULocale uLocale) { 233 if (isFrozen()) { 234 throw new UnsupportedOperationException("Attempt to modify immutable object"); 235 } 236 return setLocales(new ULocale[]{uLocale}); 237 } 238 239 /** 240 * Convenience routine for setting the locale priority list from 241 * an Accept-Language string. 242 * @see #setLocales(List locales) 243 * @param acceptLanguageString Accept-Language list, as defined by 244 * Section 14.4 of the RFC 2616 (HTTP 1.1) 245 * @return this, for chaining 246 * @hide draft / provisional / internal are hidden on Android 247 */ setLocales(String acceptLanguageString)248 public GlobalizationPreferences setLocales(String acceptLanguageString) { 249 if (isFrozen()) { 250 throw new UnsupportedOperationException("Attempt to modify immutable object"); 251 } 252 ULocale[] acceptLocales = null; 253 try { 254 acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true); 255 } catch (ParseException pe) { 256 //TODO: revisit after 3.8 257 throw new IllegalArgumentException("Invalid Accept-Language string"); 258 } 259 return setLocales(acceptLocales); 260 } 261 262 /** 263 * Convenience function to get a ResourceBundle instance using 264 * the specified base name based on the language/locale priority list 265 * stored in this object. 266 * 267 * @param baseName the base name of the resource bundle, a fully qualified 268 * class name 269 * @return a resource bundle for the given base name and locale based on the 270 * language/locale priority list stored in this object 271 * @hide draft / provisional / internal are hidden on Android 272 */ getResourceBundle(String baseName)273 public ResourceBundle getResourceBundle(String baseName) { 274 return getResourceBundle(baseName, null); 275 } 276 277 /** 278 * Convenience function to get a ResourceBundle instance using 279 * the specified base name and class loader based on the language/locale 280 * priority list stored in this object. 281 * 282 * @param baseName the base name of the resource bundle, a fully qualified 283 * class name 284 * @param loader the class object from which to load the resource bundle 285 * @return a resource bundle for the given base name and locale based on the 286 * language/locale priority list stored in this object 287 * @hide draft / provisional / internal are hidden on Android 288 */ getResourceBundle(String baseName, ClassLoader loader)289 public ResourceBundle getResourceBundle(String baseName, ClassLoader loader) { 290 UResourceBundle urb = null; 291 UResourceBundle candidate = null; 292 String actualLocaleName = null; 293 List<ULocale> fallbacks = getLocales(); 294 for (int i = 0; i < fallbacks.size(); i++) { 295 String localeName = (fallbacks.get(i)).toString(); 296 if (actualLocaleName != null && localeName.equals(actualLocaleName)) { 297 // Actual locale name in the previous round may exactly matches 298 // with the next fallback locale 299 urb = candidate; 300 break; 301 } 302 try { 303 if (loader == null) { 304 candidate = UResourceBundle.getBundleInstance(baseName, localeName); 305 } 306 else { 307 candidate = UResourceBundle.getBundleInstance(baseName, localeName, loader); 308 } 309 if (candidate != null) { 310 actualLocaleName = candidate.getULocale().getName(); 311 if (actualLocaleName.equals(localeName)) { 312 urb = candidate; 313 break; 314 } 315 if (urb == null) { 316 // Preserve the available bundle as the last resort 317 urb = candidate; 318 } 319 } 320 } catch (MissingResourceException mre) { 321 actualLocaleName = null; 322 continue; 323 } 324 } 325 if (urb == null) { 326 throw new MissingResourceException("Can't find bundle for base name " 327 + baseName, baseName, ""); 328 } 329 return urb; 330 } 331 332 /** 333 * Sets the territory, which is a valid territory according to for 334 * RFC 3066 (or successor). If not otherwise set, default 335 * currency and timezone values will be set from this. The user 336 * should be given the opportunity to correct those defaults in 337 * case they are incorrect. 338 * 339 * @param territory code 340 * @return this, for chaining 341 * @hide draft / provisional / internal are hidden on Android 342 */ setTerritory(String territory)343 public GlobalizationPreferences setTerritory(String territory) { 344 if (isFrozen()) { 345 throw new UnsupportedOperationException("Attempt to modify immutable object"); 346 } 347 this.territory = territory; // immutable, so don't need to clone 348 return this; 349 } 350 351 /** 352 * Gets the territory setting. If it wasn't explicitly set, it is 353 * computed from the general locale setting. 354 * 355 * @return territory code, explicit or implicit. 356 * @hide draft / provisional / internal are hidden on Android 357 */ getTerritory()358 public String getTerritory() { 359 if (territory == null) { 360 return guessTerritory(); 361 } 362 return territory; // immutable, so don't need to clone 363 } 364 365 /** 366 * Sets the currency code. If this has not been set, uses default for territory. 367 * 368 * @param currency Valid ISO 4217 currency code. 369 * @return this, for chaining 370 * @hide draft / provisional / internal are hidden on Android 371 */ setCurrency(Currency currency)372 public GlobalizationPreferences setCurrency(Currency currency) { 373 if (isFrozen()) { 374 throw new UnsupportedOperationException("Attempt to modify immutable object"); 375 } 376 this.currency = currency; // immutable, so don't need to clone 377 return this; 378 } 379 380 /** 381 * Get a copy of the currency computed according to the settings. 382 * 383 * @return currency code, explicit or implicit. 384 * @hide draft / provisional / internal are hidden on Android 385 */ getCurrency()386 public Currency getCurrency() { 387 if (currency == null) { 388 return guessCurrency(); 389 } 390 return currency; // immutable, so don't have to clone 391 } 392 393 /** 394 * Sets the calendar. If this has not been set, uses default for territory. 395 * 396 * @param calendar arbitrary calendar 397 * @return this, for chaining 398 * @hide draft / provisional / internal are hidden on Android 399 */ setCalendar(Calendar calendar)400 public GlobalizationPreferences setCalendar(Calendar calendar) { 401 if (isFrozen()) { 402 throw new UnsupportedOperationException("Attempt to modify immutable object"); 403 } 404 this.calendar = (Calendar) calendar.clone(); // clone for safety 405 return this; 406 } 407 408 /** 409 * Get a copy of the calendar according to the settings. 410 * 411 * @return calendar explicit or implicit. 412 * @hide draft / provisional / internal are hidden on Android 413 */ getCalendar()414 public Calendar getCalendar() { 415 if (calendar == null) { 416 return guessCalendar(); 417 } 418 Calendar temp = (Calendar) calendar.clone(); // clone for safety 419 temp.setTimeZone(getTimeZone()); 420 temp.setTimeInMillis(System.currentTimeMillis()); 421 return temp; 422 } 423 424 /** 425 * Sets the timezone ID. If this has not been set, uses default for territory. 426 * 427 * @param timezone a valid TZID (see UTS#35). 428 * @return this, for chaining 429 * @hide draft / provisional / internal are hidden on Android 430 */ setTimeZone(TimeZone timezone)431 public GlobalizationPreferences setTimeZone(TimeZone timezone) { 432 if (isFrozen()) { 433 throw new UnsupportedOperationException("Attempt to modify immutable object"); 434 } 435 this.timezone = (TimeZone) timezone.clone(); // clone for safety; 436 return this; 437 } 438 439 /** 440 * Get the timezone. It was either explicitly set, or is 441 * heuristically computed from other settings. 442 * 443 * @return timezone, either implicitly or explicitly set 444 * @hide draft / provisional / internal are hidden on Android 445 */ getTimeZone()446 public TimeZone getTimeZone() { 447 if (timezone == null) { 448 return guessTimeZone(); 449 } 450 return timezone.cloneAsThawed(); // clone for safety 451 } 452 453 /** 454 * Get a copy of the collator according to the settings. 455 * 456 * @return collator explicit or implicit. 457 * @hide draft / provisional / internal are hidden on Android 458 */ getCollator()459 public Collator getCollator() { 460 if (collator == null) { 461 return guessCollator(); 462 } 463 try { 464 return (Collator) collator.clone(); // clone for safety 465 } catch (CloneNotSupportedException e) { 466 throw new ICUCloneNotSupportedException("Error in cloning collator", e); 467 } 468 } 469 470 /** 471 * Explicitly set the collator for this object. 472 * @param collator The collator object to be passed. 473 * @return this, for chaining 474 * @hide draft / provisional / internal are hidden on Android 475 */ setCollator(Collator collator)476 public GlobalizationPreferences setCollator(Collator collator) { 477 if (isFrozen()) { 478 throw new UnsupportedOperationException("Attempt to modify immutable object"); 479 } 480 try { 481 this.collator = (Collator) collator.clone(); // clone for safety 482 } catch (CloneNotSupportedException e) { 483 throw new ICUCloneNotSupportedException("Error in cloning collator", e); 484 } 485 return this; 486 } 487 488 /** 489 * Get a copy of the break iterator for the specified type according to the 490 * settings. 491 * 492 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE 493 * @return break iterator explicit or implicit 494 * @hide draft / provisional / internal are hidden on Android 495 */ getBreakIterator(int type)496 public BreakIterator getBreakIterator(int type) { 497 if (type < BI_CHARACTER || type >= BI_LIMIT) { 498 throw new IllegalArgumentException("Illegal break iterator type"); 499 } 500 if (breakIterators == null || breakIterators[type] == null) { 501 return guessBreakIterator(type); 502 } 503 return (BreakIterator) breakIterators[type].clone(); // clone for safety 504 } 505 506 /** 507 * Explicitly set the break iterator for this object. 508 * 509 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE 510 * @param iterator a break iterator 511 * @return this, for chaining 512 * @hide draft / provisional / internal are hidden on Android 513 */ setBreakIterator(int type, BreakIterator iterator)514 public GlobalizationPreferences setBreakIterator(int type, BreakIterator iterator) { 515 if (type < BI_CHARACTER || type >= BI_LIMIT) { 516 throw new IllegalArgumentException("Illegal break iterator type"); 517 } 518 if (isFrozen()) { 519 throw new UnsupportedOperationException("Attempt to modify immutable object"); 520 } 521 if (breakIterators == null) 522 breakIterators = new BreakIterator[BI_LIMIT]; 523 breakIterators[type] = (BreakIterator) iterator.clone(); // clone for safety 524 return this; 525 } 526 527 /** 528 * Get the display name for an ID: language, script, territory, currency, timezone... 529 * Uses the language priority list to do so. 530 * 531 * @param id language code, script code, ... 532 * @param type specifies the type of the ID: ID_LANGUAGE, etc. 533 * @return the display name 534 * @hide draft / provisional / internal are hidden on Android 535 */ getDisplayName(String id, int type)536 public String getDisplayName(String id, int type) { 537 String result = id; 538 for (ULocale locale : getLocales()) { 539 if (!isAvailableLocale(locale, TYPE_GENERIC)) { 540 continue; 541 } 542 switch (type) { 543 case ID_LOCALE: 544 result = ULocale.getDisplayName(id, locale); 545 break; 546 case ID_LANGUAGE: 547 result = ULocale.getDisplayLanguage(id, locale); 548 break; 549 case ID_SCRIPT: 550 result = ULocale.getDisplayScript("und-" + id, locale); 551 break; 552 case ID_TERRITORY: 553 result = ULocale.getDisplayCountry("und-" + id, locale); 554 break; 555 case ID_VARIANT: 556 // TODO fix variant parsing 557 result = ULocale.getDisplayVariant("und-QQ-" + id, locale); 558 break; 559 case ID_KEYWORD: 560 result = ULocale.getDisplayKeyword(id, locale); 561 break; 562 case ID_KEYWORD_VALUE: 563 String[] parts = new String[2]; 564 Utility.split(id,'=',parts); 565 result = ULocale.getDisplayKeywordValue("und@"+id, parts[0], locale); 566 // TODO fix to tell when successful 567 if (result.equals(parts[1])) { 568 continue; 569 } 570 break; 571 case ID_CURRENCY_SYMBOL: 572 case ID_CURRENCY: 573 Currency temp = new Currency(id); 574 result =temp.getName(locale, type==ID_CURRENCY 575 ? Currency.LONG_NAME 576 : Currency.SYMBOL_NAME, new boolean[1]); 577 // TODO: have method that doesn't take parameter. Add 578 // function to determine whether string is choice 579 // format. 580 // TODO: have method that doesn't require us 581 // to create a currency 582 break; 583 case ID_TIMEZONE: 584 SimpleDateFormat dtf = new SimpleDateFormat("vvvv",locale); 585 dtf.setTimeZone(TimeZone.getFrozenTimeZone(id)); 586 result = dtf.format(new Date()); 587 // TODO, have method that doesn't require us to create a timezone 588 // fix other hacks 589 // hack for couldn't match 590 591 boolean isBadStr = false; 592 // Matcher badTimeZone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher(""); 593 // badtzstr = badTimeZone.reset(result).matches(); 594 String teststr = result; 595 int sidx = result.indexOf('('); 596 int eidx = result.indexOf(')'); 597 if (sidx != -1 && eidx != -1 && (eidx - sidx) == 3) { 598 teststr = result.substring(sidx+1, eidx); 599 } 600 if (teststr.length() == 2) { 601 isBadStr = true; 602 for (int i = 0; i < 2; i++) { 603 char c = teststr.charAt(i); 604 if (c < 'A' || 'Z' < c) { 605 isBadStr = false; 606 break; 607 } 608 } 609 } 610 if (isBadStr) { 611 continue; 612 } 613 break; 614 default: 615 throw new IllegalArgumentException("Unknown type: " + type); 616 } 617 618 // TODO need better way of seeing if we fell back to root!! 619 // This will not work at all for lots of stuff 620 if (!id.equals(result)) { 621 return result; 622 } 623 } 624 return result; 625 } 626 627 /** 628 * Set an explicit date format. Overrides the locale priority list for 629 * a particular combination of dateStyle and timeStyle. DF_NONE should 630 * be used if for the style, where only the date or time format individually 631 * is being set. 632 * 633 * @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 634 * @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 635 * @param format The date format 636 * @return this, for chaining 637 * @hide draft / provisional / internal are hidden on Android 638 */ setDateFormat(int dateStyle, int timeStyle, DateFormat format)639 public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) { 640 if (isFrozen()) { 641 throw new UnsupportedOperationException("Attempt to modify immutable object"); 642 } 643 if (dateFormats == null) { 644 dateFormats = new DateFormat[DF_LIMIT][DF_LIMIT]; 645 } 646 dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety 647 return this; 648 } 649 650 /** 651 * Gets a date format according to the current settings. If there 652 * is an explicit (non-null) date/time format set, a copy of that 653 * is returned. Otherwise, the language priority list is used. 654 * DF_NONE should be used for the style, where only the date or 655 * time format individually is being gotten. 656 * 657 * @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 658 * @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 659 * @return a DateFormat, according to the above description 660 * @hide draft / provisional / internal are hidden on Android 661 */ getDateFormat(int dateStyle, int timeStyle)662 public DateFormat getDateFormat(int dateStyle, int timeStyle) { 663 if (dateStyle == DF_NONE && timeStyle == DF_NONE 664 || dateStyle < 0 || dateStyle >= DF_LIMIT 665 || timeStyle < 0 || timeStyle >= DF_LIMIT) { 666 throw new IllegalArgumentException("Illegal date format style arguments"); 667 } 668 DateFormat result = null; 669 if (dateFormats != null) { 670 result = dateFormats[dateStyle][timeStyle]; 671 } 672 if (result != null) { 673 result = (DateFormat) result.clone(); // clone for safety 674 // Not sure overriding configuration is what we really want... 675 result.setTimeZone(getTimeZone()); 676 } else { 677 result = guessDateFormat(dateStyle, timeStyle); 678 } 679 return result; 680 } 681 682 /** 683 * Gets a number format according to the current settings. If 684 * there is an explicit (non-null) number format set, a copy of 685 * that is returned. Otherwise, the language priority list is 686 * used. 687 * 688 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER 689 * @hide draft / provisional / internal are hidden on Android 690 */ getNumberFormat(int style)691 public NumberFormat getNumberFormat(int style) { 692 if (style < 0 || style >= NF_LIMIT) { 693 throw new IllegalArgumentException("Illegal number format type"); 694 } 695 NumberFormat result = null; 696 if (numberFormats != null) { 697 result = numberFormats[style]; 698 } 699 if (result != null) { 700 result = (NumberFormat) result.clone(); // clone for safety (later optimize) 701 } else { 702 result = guessNumberFormat(style); 703 } 704 return result; 705 } 706 707 /** 708 * Sets a number format explicitly. Overrides the general locale settings. 709 * 710 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER 711 * @param format The number format 712 * @return this, for chaining 713 * @hide draft / provisional / internal are hidden on Android 714 */ setNumberFormat(int style, NumberFormat format)715 public GlobalizationPreferences setNumberFormat(int style, NumberFormat format) { 716 if (isFrozen()) { 717 throw new UnsupportedOperationException("Attempt to modify immutable object"); 718 } 719 if (numberFormats == null) { 720 numberFormats = new NumberFormat[NF_LIMIT]; 721 } 722 numberFormats[style] = (NumberFormat) format.clone(); // for safety 723 return this; 724 } 725 726 /** 727 * Restore the object to the initial state. 728 * 729 * @return this, for chaining 730 * @hide draft / provisional / internal are hidden on Android 731 */ reset()732 public GlobalizationPreferences reset() { 733 if (isFrozen()) { 734 throw new UnsupportedOperationException("Attempt to modify immutable object"); 735 } 736 locales = null; 737 territory = null; 738 calendar = null; 739 collator = null; 740 breakIterators = null; 741 timezone = null; 742 currency = null; 743 dateFormats = null; 744 numberFormats = null; 745 implicitLocales = null; 746 return this; 747 } 748 749 /** 750 * Process a language/locale priority list specified via <code>setLocales</code>. 751 * The input locale list may be expanded or re-ordered to represent the prioritized 752 * language/locale order actually used by this object by the algorithm explained 753 * below. 754 * <br> 755 * <br> 756 * <b>Step 1</b>: Move later occurrence of more specific locale before earlier 757 * occurrence of less specific locale. 758 * <br> 759 * Before: en, fr_FR, en_US, en_GB 760 * <br> 761 * After: en_US, en_GB, en, fr_FR 762 * <br> 763 * <br> 764 * <b>Step 2</b>: Append a fallback locale to each locale. 765 * <br> 766 * Before: en_US, en_GB, en, fr_FR 767 * <br> 768 * After: en_US, en, en_GB, en, en, fr_FR, fr 769 * <br> 770 * <br> 771 * <b>Step 3</b>: Remove earlier occurrence of duplicated locale entries. 772 * <br> 773 * Before: en_US, en, en_GB, en, en, fr_FR, fr 774 * <br> 775 * After: en_US, en_GB, en, fr_FR, fr 776 * <br> 777 * <br> 778 * The final locale list is used to produce a default value for the appropriate territory, 779 * currency, timezone, etc. The list also represents the lookup order used in 780 * <code>getResourceBundle</code> for this object. A subclass may override this method 781 * to customize the algorithm used for populating the locale list. 782 * 783 * @param inputLocales The list of input locales 784 * @hide draft / provisional / internal are hidden on Android 785 */ processLocales(List<ULocale> inputLocales)786 protected List<ULocale> processLocales(List<ULocale> inputLocales) { 787 List<ULocale> result = new ArrayList<ULocale>(); 788 /* 789 * Step 1: Relocate later occurrence of more specific locale 790 * before earlier occurrence of less specific locale. 791 * 792 * Example: 793 * Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA 794 * After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA 795 */ 796 for (int i = 0; i < inputLocales.size(); i++) { 797 ULocale uloc = inputLocales.get(i); 798 799 String language = uloc.getLanguage(); 800 String script = uloc.getScript(); 801 String country = uloc.getCountry(); 802 String variant = uloc.getVariant(); 803 804 boolean bInserted = false; 805 for (int j = 0; j < result.size(); j++) { 806 // Check if this locale is more specific 807 // than existing locale entries already inserted 808 // in the destination list 809 ULocale u = result.get(j); 810 if (!u.getLanguage().equals(language)) { 811 continue; 812 } 813 String s = u.getScript(); 814 String c = u.getCountry(); 815 String v = u.getVariant(); 816 if (!s.equals(script)) { 817 if (s.length() == 0 && c.length() == 0 && v.length() == 0) { 818 result.add(j, uloc); 819 bInserted = true; 820 break; 821 } else if (s.length() == 0 && c.equals(country)) { 822 // We want to see zh_Hant_HK before zh_HK 823 result.add(j, uloc); 824 bInserted = true; 825 break; 826 } else if (script.length() == 0 && country.length() > 0 && c.length() == 0) { 827 // We want to see zh_HK before zh_Hant 828 result.add(j, uloc); 829 bInserted = true; 830 break; 831 } 832 continue; 833 } 834 if (!c.equals(country)) { 835 if (c.length() == 0 && v.length() == 0) { 836 result.add(j, uloc); 837 bInserted = true; 838 break; 839 } 840 } 841 if (!v.equals(variant) && v.length() == 0) { 842 result.add(j, uloc); 843 bInserted = true; 844 break; 845 } 846 } 847 if (!bInserted) { 848 // Add this locale at the end of the list 849 result.add(uloc); 850 } 851 } 852 853 // TODO: Locale aliases might be resolved here 854 // For example, zh_Hant_TW = zh_TW 855 856 /* 857 * Step 2: Append fallback locales for each entry 858 * 859 * Example: 860 * Before - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA 861 * After - en_US_Boston, en_US, en, en_US, en, fr_FR, fr, 862 * zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr 863 */ 864 int index = 0; 865 while (index < result.size()) { 866 ULocale uloc = result.get(index); 867 while ((uloc = uloc.getFallback()) != null) { 868 if (uloc.getLanguage().length() == 0) { 869 break; 870 } 871 index++; 872 result.add(index, uloc); 873 } 874 index++; 875 } 876 877 /* 878 * Step 3: Remove earlier occurrence of duplicated locales 879 * 880 * Example: 881 * Before - en_US_Boston, en_US, en, en_US, en, fr_FR, fr, 882 * zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr 883 * After - en_US_Boston, en_US, en, fr_FR, zh_TW, zh_Hant, 884 * zh, fr_CA, fr 885 */ 886 index = 0; 887 while (index < result.size() - 1) { 888 ULocale uloc = result.get(index); 889 boolean bRemoved = false; 890 for (int i = index + 1; i < result.size(); i++) { 891 if (uloc.equals(result.get(i))) { 892 // Remove earlier one 893 result.remove(index); 894 bRemoved = true; 895 break; 896 } 897 } 898 if (!bRemoved) { 899 index++; 900 } 901 } 902 return result; 903 } 904 905 906 /** 907 * This function can be overridden by subclasses to use different heuristics. 908 * <b>It MUST return a 'safe' value, 909 * one whose modification will not affect this object.</b> 910 * 911 * @param dateStyle 912 * @param timeStyle 913 * @hide draft / provisional / internal are hidden on Android 914 */ guessDateFormat(int dateStyle, int timeStyle)915 protected DateFormat guessDateFormat(int dateStyle, int timeStyle) { 916 DateFormat result; 917 ULocale dfLocale = getAvailableLocale(TYPE_DATEFORMAT); 918 if (dfLocale == null) { 919 dfLocale = ULocale.ROOT; 920 } 921 if (timeStyle == DF_NONE) { 922 result = DateFormat.getDateInstance(getCalendar(), dateStyle, dfLocale); 923 } else if (dateStyle == DF_NONE) { 924 result = DateFormat.getTimeInstance(getCalendar(), timeStyle, dfLocale); 925 } else { 926 result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, dfLocale); 927 } 928 return result; 929 } 930 931 /** 932 * This function can be overridden by subclasses to use different heuristics. 933 * <b>It MUST return a 'safe' value, 934 * one whose modification will not affect this object.</b> 935 * 936 * @param style 937 * @hide draft / provisional / internal are hidden on Android 938 */ guessNumberFormat(int style)939 protected NumberFormat guessNumberFormat(int style) { 940 NumberFormat result; 941 ULocale nfLocale = getAvailableLocale(TYPE_NUMBERFORMAT); 942 if (nfLocale == null) { 943 nfLocale = ULocale.ROOT; 944 } 945 switch (style) { 946 case NF_NUMBER: 947 result = NumberFormat.getInstance(nfLocale); 948 break; 949 case NF_SCIENTIFIC: 950 result = NumberFormat.getScientificInstance(nfLocale); 951 break; 952 case NF_INTEGER: 953 result = NumberFormat.getIntegerInstance(nfLocale); 954 break; 955 case NF_PERCENT: 956 result = NumberFormat.getPercentInstance(nfLocale); 957 break; 958 case NF_CURRENCY: 959 result = NumberFormat.getCurrencyInstance(nfLocale); 960 result.setCurrency(getCurrency()); 961 break; 962 default: 963 throw new IllegalArgumentException("Unknown number format style"); 964 } 965 return result; 966 } 967 968 /** 969 * This function can be overridden by subclasses to use different heuristics. 970 * 971 * @hide draft / provisional / internal are hidden on Android 972 */ guessTerritory()973 protected String guessTerritory() { 974 String result; 975 // pass through locales to see if there is a territory. 976 for (ULocale locale : getLocales()) { 977 result = locale.getCountry(); 978 if (result.length() != 0) { 979 return result; 980 } 981 } 982 // if not, guess from the first language tag, or maybe from 983 // intersection of languages, eg nl + fr => BE 984 // TODO: fix using real data 985 // for now, just use fixed values 986 ULocale firstLocale = getLocale(0); 987 String language = firstLocale.getLanguage(); 988 String script = firstLocale.getScript(); 989 result = null; 990 if (script.length() != 0) { 991 result = language_territory_hack_map.get(language + "_" + script); 992 } 993 if (result == null) { 994 result = language_territory_hack_map.get(language); 995 } 996 if (result == null) { 997 result = "US"; // need *some* default 998 } 999 return result; 1000 } 1001 1002 /** 1003 * This function can be overridden by subclasses to use different heuristics 1004 * 1005 * @hide draft / provisional / internal are hidden on Android 1006 */ guessCurrency()1007 protected Currency guessCurrency() { 1008 return Currency.getInstance(new ULocale("und-" + getTerritory())); 1009 } 1010 1011 /** 1012 * This function can be overridden by subclasses to use different heuristics 1013 * <b>It MUST return a 'safe' value, 1014 * one whose modification will not affect this object.</b> 1015 * 1016 * @hide draft / provisional / internal are hidden on Android 1017 */ guessLocales()1018 protected List<ULocale> guessLocales() { 1019 if (implicitLocales == null) { 1020 List<ULocale> result = new ArrayList<ULocale>(1); 1021 result.add(ULocale.getDefault()); 1022 implicitLocales = processLocales(result); 1023 } 1024 return implicitLocales; 1025 } 1026 1027 /** 1028 * This function can be overridden by subclasses to use different heuristics. 1029 * <b>It MUST return a 'safe' value, 1030 * one whose modification will not affect this object.</b> 1031 * 1032 * @hide draft / provisional / internal are hidden on Android 1033 */ guessCollator()1034 protected Collator guessCollator() { 1035 ULocale collLocale = getAvailableLocale(TYPE_COLLATOR); 1036 if (collLocale == null) { 1037 collLocale = ULocale.ROOT; 1038 } 1039 return Collator.getInstance(collLocale); 1040 } 1041 1042 /** 1043 * This function can be overridden by subclasses to use different heuristics. 1044 * <b>It MUST return a 'safe' value, 1045 * one whose modification will not affect this object.</b> 1046 * 1047 * @param type 1048 * @hide draft / provisional / internal are hidden on Android 1049 */ guessBreakIterator(int type)1050 protected BreakIterator guessBreakIterator(int type) { 1051 BreakIterator bitr = null; 1052 ULocale brkLocale = getAvailableLocale(TYPE_BREAKITERATOR); 1053 if (brkLocale == null) { 1054 brkLocale = ULocale.ROOT; 1055 } 1056 switch (type) { 1057 case BI_CHARACTER: 1058 bitr = BreakIterator.getCharacterInstance(brkLocale); 1059 break; 1060 case BI_TITLE: 1061 bitr = BreakIterator.getTitleInstance(brkLocale); 1062 break; 1063 case BI_WORD: 1064 bitr = BreakIterator.getWordInstance(brkLocale); 1065 break; 1066 case BI_LINE: 1067 bitr = BreakIterator.getLineInstance(brkLocale); 1068 break; 1069 case BI_SENTENCE: 1070 bitr = BreakIterator.getSentenceInstance(brkLocale); 1071 break; 1072 default: 1073 throw new IllegalArgumentException("Unknown break iterator type"); 1074 } 1075 return bitr; 1076 } 1077 1078 /** 1079 * This function can be overridden by subclasses to use different heuristics. 1080 * <b>It MUST return a 'safe' value, 1081 * one whose modification will not affect this object.</b> 1082 * 1083 * @hide draft / provisional / internal are hidden on Android 1084 */ guessTimeZone()1085 protected TimeZone guessTimeZone() { 1086 // TODO fix using real data 1087 // for single-zone countries, pick that zone 1088 // for others, pick the most populous zone 1089 // for now, just use fixed value 1090 // NOTE: in a few cases can do better by looking at language. 1091 // Eg haw+US should go to Pacific/Honolulu 1092 // fr+CA should go to America/Montreal 1093 String timezoneString = territory_tzid_hack_map.get(getTerritory()); 1094 if (timezoneString == null) { 1095 String[] attempt = TimeZone.getAvailableIDs(getTerritory()); 1096 if (attempt.length == 0) { 1097 timezoneString = "Etc/GMT"; // gotta do something 1098 } else { 1099 int i; 1100 // this all needs to be fixed to use real data. But for now, do slightly better by skipping cruft 1101 for (i = 0; i < attempt.length; ++i) { 1102 if (attempt[i].indexOf("/") >= 0) break; 1103 } 1104 if (i > attempt.length) i = 0; 1105 timezoneString = attempt[i]; 1106 } 1107 } 1108 return TimeZone.getTimeZone(timezoneString); 1109 } 1110 1111 /** 1112 * This function can be overridden by subclasses to use different heuristics. 1113 * <b>It MUST return a 'safe' value, 1114 * one whose modification will not affect this object.</b> 1115 * 1116 * @hide draft / provisional / internal are hidden on Android 1117 */ guessCalendar()1118 protected Calendar guessCalendar() { 1119 ULocale calLocale = getAvailableLocale(TYPE_CALENDAR); 1120 if (calLocale == null) { 1121 calLocale = ULocale.US; 1122 } 1123 return Calendar.getInstance(getTimeZone(), calLocale); 1124 } 1125 1126 // PRIVATES 1127 1128 private List<ULocale> locales; 1129 private String territory; 1130 private Currency currency; 1131 private TimeZone timezone; 1132 private Calendar calendar; 1133 private Collator collator; 1134 private BreakIterator[] breakIterators; 1135 private DateFormat[][] dateFormats; 1136 private NumberFormat[] numberFormats; 1137 private List<ULocale> implicitLocales; 1138 1139 { reset()1140 reset(); 1141 } 1142 1143 getAvailableLocale(int type)1144 private ULocale getAvailableLocale(int type) { 1145 List<ULocale> locs = getLocales(); 1146 ULocale result = null; 1147 for (int i = 0; i < locs.size(); i++) { 1148 ULocale l = locs.get(i); 1149 if (isAvailableLocale(l, type)) { 1150 result = l; 1151 break; 1152 } 1153 } 1154 return result; 1155 } 1156 isAvailableLocale(ULocale loc, int type)1157 private boolean isAvailableLocale(ULocale loc, int type) { 1158 BitSet bits = available_locales.get(loc); 1159 if (bits != null && bits.get(type)) { 1160 return true; 1161 } 1162 return false; 1163 } 1164 1165 /* 1166 * Available locales for service types 1167 */ 1168 private static final HashMap<ULocale, BitSet> available_locales = new HashMap<ULocale, BitSet>(); 1169 private static final int 1170 TYPE_GENERIC = 0, 1171 TYPE_CALENDAR = 1, 1172 TYPE_DATEFORMAT= 2, 1173 TYPE_NUMBERFORMAT = 3, 1174 TYPE_COLLATOR = 4, 1175 TYPE_BREAKITERATOR = 5, 1176 TYPE_LIMIT = TYPE_BREAKITERATOR + 1; 1177 1178 static { 1179 BitSet bits; 1180 ULocale[] allLocales = ULocale.getAvailableLocales(); 1181 for (int i = 0; i < allLocales.length; i++) { 1182 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1183 available_locales.put(allLocales[i], bits); 1184 bits.set(TYPE_GENERIC); 1185 } 1186 1187 ULocale[] calLocales = Calendar.getAvailableULocales(); 1188 for (int i = 0; i < calLocales.length; i++) { 1189 bits = available_locales.get(calLocales[i]); 1190 if (bits == null) { 1191 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1192 available_locales.put(allLocales[i], bits); 1193 } 1194 bits.set(TYPE_CALENDAR); 1195 } 1196 1197 ULocale[] dateLocales = DateFormat.getAvailableULocales(); 1198 for (int i = 0; i < dateLocales.length; i++) { 1199 bits = available_locales.get(dateLocales[i]); 1200 if (bits == null) { 1201 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1202 available_locales.put(allLocales[i], bits); 1203 } 1204 bits.set(TYPE_DATEFORMAT); 1205 } 1206 1207 ULocale[] numLocales = NumberFormat.getAvailableULocales(); 1208 for (int i = 0; i < numLocales.length; i++) { 1209 bits = available_locales.get(numLocales[i]); 1210 if (bits == null) { 1211 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1212 available_locales.put(allLocales[i], bits); 1213 } 1214 bits.set(TYPE_NUMBERFORMAT); 1215 } 1216 1217 ULocale[] collLocales = Collator.getAvailableULocales(); 1218 for (int i = 0; i < collLocales.length; i++) { 1219 bits = available_locales.get(collLocales[i]); 1220 if (bits == null) { 1221 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1222 available_locales.put(allLocales[i], bits); 1223 } 1224 bits.set(TYPE_COLLATOR); 1225 } 1226 1227 ULocale[] brkLocales = BreakIterator.getAvailableULocales(); 1228 for (int i = 0; i < brkLocales.length; i++) { 1229 bits = available_locales.get(brkLocales[i]); 1230 bits.set(TYPE_BREAKITERATOR); 1231 } 1232 } 1233 1234 /** WARNING: All of this data is temporary, until we start importing from CLDR!!! 1235 * 1236 */ 1237 private static final Map<String, String> language_territory_hack_map = new HashMap<String, String>(); 1238 private static final String[][] language_territory_hack = { 1239 {"af", "ZA"}, 1240 {"am", "ET"}, 1241 {"ar", "SA"}, 1242 {"as", "IN"}, 1243 {"ay", "PE"}, 1244 {"az", "AZ"}, 1245 {"bal", "PK"}, 1246 {"be", "BY"}, 1247 {"bg", "BG"}, 1248 {"bn", "IN"}, 1249 {"bs", "BA"}, 1250 {"ca", "ES"}, 1251 {"ch", "MP"}, 1252 {"cpe", "SL"}, 1253 {"cs", "CZ"}, 1254 {"cy", "GB"}, 1255 {"da", "DK"}, 1256 {"de", "DE"}, 1257 {"dv", "MV"}, 1258 {"dz", "BT"}, 1259 {"el", "GR"}, 1260 {"en", "US"}, 1261 {"es", "ES"}, 1262 {"et", "EE"}, 1263 {"eu", "ES"}, 1264 {"fa", "IR"}, 1265 {"fi", "FI"}, 1266 {"fil", "PH"}, 1267 {"fj", "FJ"}, 1268 {"fo", "FO"}, 1269 {"fr", "FR"}, 1270 {"ga", "IE"}, 1271 {"gd", "GB"}, 1272 {"gl", "ES"}, 1273 {"gn", "PY"}, 1274 {"gu", "IN"}, 1275 {"gv", "GB"}, 1276 {"ha", "NG"}, 1277 {"he", "IL"}, 1278 {"hi", "IN"}, 1279 {"ho", "PG"}, 1280 {"hr", "HR"}, 1281 {"ht", "HT"}, 1282 {"hu", "HU"}, 1283 {"hy", "AM"}, 1284 {"id", "ID"}, 1285 {"is", "IS"}, 1286 {"it", "IT"}, 1287 {"ja", "JP"}, 1288 {"ka", "GE"}, 1289 {"kk", "KZ"}, 1290 {"kl", "GL"}, 1291 {"km", "KH"}, 1292 {"kn", "IN"}, 1293 {"ko", "KR"}, 1294 {"kok", "IN"}, 1295 {"ks", "IN"}, 1296 {"ku", "TR"}, 1297 {"ky", "KG"}, 1298 {"la", "VA"}, 1299 {"lb", "LU"}, 1300 {"ln", "CG"}, 1301 {"lo", "LA"}, 1302 {"lt", "LT"}, 1303 {"lv", "LV"}, 1304 {"mai", "IN"}, 1305 {"men", "GN"}, 1306 {"mg", "MG"}, 1307 {"mh", "MH"}, 1308 {"mk", "MK"}, 1309 {"ml", "IN"}, 1310 {"mn", "MN"}, 1311 {"mni", "IN"}, 1312 {"mo", "MD"}, 1313 {"mr", "IN"}, 1314 {"ms", "MY"}, 1315 {"mt", "MT"}, 1316 {"my", "MM"}, 1317 {"na", "NR"}, 1318 {"nb", "NO"}, 1319 {"nd", "ZA"}, 1320 {"ne", "NP"}, 1321 {"niu", "NU"}, 1322 {"nl", "NL"}, 1323 {"nn", "NO"}, 1324 {"no", "NO"}, 1325 {"nr", "ZA"}, 1326 {"nso", "ZA"}, 1327 {"ny", "MW"}, 1328 {"om", "KE"}, 1329 {"or", "IN"}, 1330 {"pa", "IN"}, 1331 {"pau", "PW"}, 1332 {"pl", "PL"}, 1333 {"ps", "PK"}, 1334 {"pt", "BR"}, 1335 {"qu", "PE"}, 1336 {"rn", "BI"}, 1337 {"ro", "RO"}, 1338 {"ru", "RU"}, 1339 {"rw", "RW"}, 1340 {"sd", "IN"}, 1341 {"sg", "CF"}, 1342 {"si", "LK"}, 1343 {"sk", "SK"}, 1344 {"sl", "SI"}, 1345 {"sm", "WS"}, 1346 {"so", "DJ"}, 1347 {"sq", "CS"}, 1348 {"sr", "CS"}, 1349 {"ss", "ZA"}, 1350 {"st", "ZA"}, 1351 {"sv", "SE"}, 1352 {"sw", "KE"}, 1353 {"ta", "IN"}, 1354 {"te", "IN"}, 1355 {"tem", "SL"}, 1356 {"tet", "TL"}, 1357 {"th", "TH"}, 1358 {"ti", "ET"}, 1359 {"tg", "TJ"}, 1360 {"tk", "TM"}, 1361 {"tkl", "TK"}, 1362 {"tvl", "TV"}, 1363 {"tl", "PH"}, 1364 {"tn", "ZA"}, 1365 {"to", "TO"}, 1366 {"tpi", "PG"}, 1367 {"tr", "TR"}, 1368 {"ts", "ZA"}, 1369 {"uk", "UA"}, 1370 {"ur", "IN"}, 1371 {"uz", "UZ"}, 1372 {"ve", "ZA"}, 1373 {"vi", "VN"}, 1374 {"wo", "SN"}, 1375 {"xh", "ZA"}, 1376 {"zh", "CN"}, 1377 {"zh_Hant", "TW"}, 1378 {"zu", "ZA"}, 1379 {"aa", "ET"}, 1380 {"byn", "ER"}, 1381 {"eo", "DE"}, 1382 {"gez", "ET"}, 1383 {"haw", "US"}, 1384 {"iu", "CA"}, 1385 {"kw", "GB"}, 1386 {"sa", "IN"}, 1387 {"sh", "HR"}, 1388 {"sid", "ET"}, 1389 {"syr", "SY"}, 1390 {"tig", "ER"}, 1391 {"tt", "RU"}, 1392 {"wal", "ET"}, }; 1393 static { 1394 for (int i = 0; i < language_territory_hack.length; ++i) { language_territory_hack_map.put(language_territory_hack[i][0],language_territory_hack[i][1])1395 language_territory_hack_map.put(language_territory_hack[i][0],language_territory_hack[i][1]); 1396 } 1397 } 1398 1399 static final Map<String, String> territory_tzid_hack_map = new HashMap<String, String>(); 1400 static final String[][] territory_tzid_hack = { 1401 {"AQ", "Antarctica/McMurdo"}, 1402 {"AR", "America/Buenos_Aires"}, 1403 {"AU", "Australia/Sydney"}, 1404 {"BR", "America/Sao_Paulo"}, 1405 {"CA", "America/Toronto"}, 1406 {"CD", "Africa/Kinshasa"}, 1407 {"CL", "America/Santiago"}, 1408 {"CN", "Asia/Shanghai"}, 1409 {"EC", "America/Guayaquil"}, 1410 {"ES", "Europe/Madrid"}, 1411 {"GB", "Europe/London"}, 1412 {"GL", "America/Godthab"}, 1413 {"ID", "Asia/Jakarta"}, 1414 {"ML", "Africa/Bamako"}, 1415 {"MX", "America/Mexico_City"}, 1416 {"MY", "Asia/Kuala_Lumpur"}, 1417 {"NZ", "Pacific/Auckland"}, 1418 {"PT", "Europe/Lisbon"}, 1419 {"RU", "Europe/Moscow"}, 1420 {"UA", "Europe/Kiev"}, 1421 {"US", "America/New_York"}, 1422 {"UZ", "Asia/Tashkent"}, 1423 {"PF", "Pacific/Tahiti"}, 1424 {"FM", "Pacific/Kosrae"}, 1425 {"KI", "Pacific/Tarawa"}, 1426 {"KZ", "Asia/Almaty"}, 1427 {"MH", "Pacific/Majuro"}, 1428 {"MN", "Asia/Ulaanbaatar"}, 1429 {"SJ", "Arctic/Longyearbyen"}, 1430 {"UM", "Pacific/Midway"}, 1431 }; 1432 static { 1433 for (int i = 0; i < territory_tzid_hack.length; ++i) { territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1])1434 territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1]); 1435 } 1436 } 1437 1438 // Freezable implementation 1439 1440 private volatile boolean frozen; 1441 1442 /** 1443 * @hide draft / provisional / internal are hidden on Android 1444 */ 1445 @Override isFrozen()1446 public boolean isFrozen() { 1447 return frozen; 1448 } 1449 1450 /** 1451 * @hide draft / provisional / internal are hidden on Android 1452 */ 1453 @Override freeze()1454 public GlobalizationPreferences freeze() { 1455 frozen = true; 1456 return this; 1457 } 1458 1459 /** 1460 * @hide draft / provisional / internal are hidden on Android 1461 */ 1462 @Override cloneAsThawed()1463 public GlobalizationPreferences cloneAsThawed() { 1464 try { 1465 GlobalizationPreferences result = (GlobalizationPreferences) clone(); 1466 result.frozen = false; 1467 return result; 1468 } catch (CloneNotSupportedException e) { 1469 // will always work 1470 return null; 1471 } 1472 } 1473 } 1474 1475