10ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader/* 20ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * Copyright (C) 2015 The Android Open Source Project 30ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * 40ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * Licensed under the Apache License, Version 2.0 (the "License"); 50ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * you may not use this file except in compliance with the License. 60ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * You may obtain a copy of the License at 70ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * 80ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * http://www.apache.org/licenses/LICENSE-2.0 90ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * 100ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * Unless required by applicable law or agreed to in writing, software 110ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * distributed under the License is distributed on an "AS IS" BASIS, 120ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * See the License for the specific language governing permissions and 140ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * limitations under the License. 150ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader */ 160ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 1723cbe85610f780134cc77dd4a54732a22ed6e86eYohei Yukawapackage android.os; 180ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 192b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournaderimport android.annotation.IntRange; 20a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournaderimport android.annotation.NonNull; 210ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournaderimport android.annotation.Nullable; 22a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournaderimport android.annotation.Size; 232591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournaderimport android.icu.util.ULocale; 24a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 25a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournaderimport com.android.internal.annotations.GuardedBy; 260ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 27834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournaderimport java.util.Arrays; 28834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournaderimport java.util.Collection; 290ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournaderimport java.util.HashSet; 300ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournaderimport java.util.Locale; 310ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 320ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader/** 3366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * LocaleList is an immutable list of Locales, typically used to keep an ordered list of user 3466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * preferences for locales. 350ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader */ 36789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawapublic final class LocaleList implements Parcelable { 370ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader private final Locale[] mList; 38f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader // This is a comma-separated list of the locales in the LocaleList created at construction time, 39f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader // basically the result of running each locale's toLanguageTag() method and concatenating them 40f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader // with commas in between. 41789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @NonNull 42f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader private final String mStringRepresentation; 43f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader 440ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader private static final Locale[] sEmptyList = new Locale[0]; 45b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader private static final LocaleList sEmptyLocaleList = new LocaleList(); 460ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 4766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 4866f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Retrieves the {@link Locale} at the specified index. 4966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 5066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @param index The position to retrieve. 5166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return The {@link Locale} in the given index. 5266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 5366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri public Locale get(int index) { 5466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri return (0 <= index && index < mList.length) ? mList[index] : null; 550ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 560ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 5766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 5866f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Returns whether the {@link LocaleList} contains no {@link Locale} items. 5966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 6066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return {@code true} if this {@link LocaleList} has no {@link Locale} items, {@code false} 6166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * otherwise. 6266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 630ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader public boolean isEmpty() { 640ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader return mList.length == 0; 650ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 660ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 6766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 6866f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Returns the number of {@link Locale} items in this {@link LocaleList}. 6966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 702b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @IntRange(from=0) 710ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader public int size() { 720ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader return mList.length; 730ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 740ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 7566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 7666f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Searches this {@link LocaleList} for the specified {@link Locale} and returns the index of 7766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * the first occurrence. 7866f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 7966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @param locale The {@link Locale} to search for. 8066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return The index of the first occurrence of the {@link Locale} or {@code -1} if the item 8166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * wasn't found. 8266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 832b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @IntRange(from=-1) 842b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public int indexOf(Locale locale) { 852b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 862b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (mList[i].equals(locale)) { 872b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return i; 882b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 892b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 902b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return -1; 912b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 922b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 93b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader @Override 94b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public boolean equals(Object other) { 95b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (other == this) 96b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return true; 97b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (!(other instanceof LocaleList)) 98b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return false; 99b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader final Locale[] otherList = ((LocaleList) other).mList; 100b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (mList.length != otherList.length) 101b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return false; 1022b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 103b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (!mList[i].equals(otherList[i])) 104b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return false; 105b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 106b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return true; 107b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 108b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 109b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader @Override 110b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public int hashCode() { 111b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader int result = 1; 1122b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 113b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader result = 31 * result + mList[i].hashCode(); 114b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 115b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return result; 116b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 117b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 118b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader @Override 119b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public String toString() { 120b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader StringBuilder sb = new StringBuilder(); 121b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append("["); 1222b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 123b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append(mList[i]); 124b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (i < mList.length - 1) { 125b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append(','); 126b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 127b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 128b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append("]"); 129b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return sb.toString(); 130b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 131b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 132789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 133789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public int describeContents() { 134789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa return 0; 135789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 136789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 137789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 138789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public void writeToParcel(Parcel dest, int parcelableFlags) { 139789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa dest.writeString(mStringRepresentation); 140789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 141789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 14266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 14366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Retrieves a String representation of the language tags in this list. 14466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 145f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader @NonNull 146b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public String toLanguageTags() { 147f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader return mStringRepresentation; 148b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 149b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 150b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader /** 15166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Creates a new {@link LocaleList}. 15266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 15366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * <p>For empty lists of {@link Locale} items it is better to use {@link #getEmptyLocaleList()}, 15466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * which returns a pre-constructed empty list.</p> 15566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 1560ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * @throws NullPointerException if any of the input locales is <code>null</code>. 1570ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * @throws IllegalArgumentException if any of the input locales repeat. 1580ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader */ 15910ea92aefa7051eb432383e0b56e7c44664fd560Raph Levien public LocaleList(@NonNull Locale... list) { 16010ea92aefa7051eb432383e0b56e7c44664fd560Raph Levien if (list.length == 0) { 1610ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader mList = sEmptyList; 162f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader mStringRepresentation = ""; 1630ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } else { 1640ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader final Locale[] localeList = new Locale[list.length]; 1650ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader final HashSet<Locale> seenLocales = new HashSet<Locale>(); 166f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader final StringBuilder sb = new StringBuilder(); 1672b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < list.length; i++) { 1680ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader final Locale l = list[i]; 1690ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader if (l == null) { 1702b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new NullPointerException("list[" + i + "] is null"); 1710ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } else if (seenLocales.contains(l)) { 1722b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new IllegalArgumentException("list[" + i + "] is a repetition"); 1730ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } else { 174f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader final Locale localeClone = (Locale) l.clone(); 175f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader localeList[i] = localeClone; 176f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader sb.append(localeClone.toLanguageTag()); 177f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader if (i < list.length - 1) { 178f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader sb.append(','); 179f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader } 180f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader seenLocales.add(localeClone); 1810ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 1820ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 1830ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader mList = localeList; 184f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader mStringRepresentation = sb.toString(); 1850ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 1860ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 187b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 1882b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 1892b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * Constructs a locale list, with the topLocale moved to the front if it already is 1902b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * in otherLocales, or added to the front if it isn't. 1912b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 1922b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * {@hide} 1932b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 1942b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public LocaleList(@NonNull Locale topLocale, LocaleList otherLocales) { 1952b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (topLocale == null) { 1962b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new NullPointerException("topLocale is null"); 1972b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 1982b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 1992b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final int inputLength = (otherLocales == null) ? 0 : otherLocales.mList.length; 2002b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader int topLocaleIndex = -1; 2012b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < inputLength; i++) { 2022b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (topLocale.equals(otherLocales.mList[i])) { 2032b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader topLocaleIndex = i; 2042b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader break; 2052b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2062b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2072b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2082b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final int outputLength = inputLength + (topLocaleIndex == -1 ? 1 : 0); 2092b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final Locale[] localeList = new Locale[outputLength]; 2102b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[0] = (Locale) topLocale.clone(); 2112b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (topLocaleIndex == -1) { 2122b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // topLocale was not in otherLocales 2132b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < inputLength; i++) { 2142b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[i + 1] = (Locale) otherLocales.mList[i].clone(); 2152b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2162b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } else { 2172b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < topLocaleIndex; i++) { 2182b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[i + 1] = (Locale) otherLocales.mList[i].clone(); 2192b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2202b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = topLocaleIndex + 1; i < inputLength; i++) { 2212b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[i] = (Locale) otherLocales.mList[i].clone(); 2222b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2232b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2242b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2252b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final StringBuilder sb = new StringBuilder(); 2262b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < outputLength; i++) { 2272b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sb.append(localeList[i].toLanguageTag()); 2282b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (i < outputLength - 1) { 2292b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sb.append(','); 2302b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2312b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2322b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2332b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader mList = localeList; 2342b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader mStringRepresentation = sb.toString(); 2352b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2362b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 237789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public static final Parcelable.Creator<LocaleList> CREATOR 238789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa = new Parcelable.Creator<LocaleList>() { 239789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 240789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public LocaleList createFromParcel(Parcel source) { 241789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa return LocaleList.forLanguageTags(source.readString()); 242789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 243789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 244789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 245789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public LocaleList[] newArray(int size) { 246789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa return new LocaleList[size]; 247789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 248789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa }; 249789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 25066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 25166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Retrieve an empty instance of {@link LocaleList}. 25266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 253789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @NonNull 254b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public static LocaleList getEmptyLocaleList() { 255b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return sEmptyLocaleList; 256b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 257b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 25866f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 25966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Generates a new LocaleList with the given language tags. 26066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 26166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @param list The language tags to be included as a single {@link String} separated by commas. 26266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return A new instance with the {@link Locale} items identified by the given tags. 26366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 264789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @NonNull 265b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public static LocaleList forLanguageTags(@Nullable String list) { 266b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (list == null || list.equals("")) { 267b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return getEmptyLocaleList(); 268b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } else { 269b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader final String[] tags = list.split(","); 270b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader final Locale[] localeArray = new Locale[tags.length]; 2712b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < localeArray.length; i++) { 272b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader localeArray[i] = Locale.forLanguageTag(tags[i]); 273b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 274b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return new LocaleList(localeArray); 275b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 276b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 277a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 2782591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader private static String getLikelyScript(Locale locale) { 2792591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader final String script = locale.getScript(); 2802591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (!script.isEmpty()) { 2812591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return script; 2822591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } else { 2832591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader // TODO: Cache the results if this proves to be too slow 2842591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript(); 2852591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 2862591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 2872591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader 2881c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final String STRING_EN_XA = "en-XA"; 2891c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final String STRING_AR_XB = "ar-XB"; 2901c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final Locale LOCALE_EN_XA = new Locale("en", "XA"); 2911c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final Locale LOCALE_AR_XB = new Locale("ar", "XB"); 2921c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final int NUM_PSEUDO_LOCALES = 2; 2931c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 2941c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static boolean isPseudoLocale(String locale) { 2951c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale); 2961c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 2971c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 2981c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static boolean isPseudoLocale(Locale locale) { 2991c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale); 3001c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 3011c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 3022b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @IntRange(from=0, to=1) 3032591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader private static int matchScore(Locale supported, Locale desired) { 3042591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (supported.equals(desired)) { 3052591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return 1; // return early so we don't do unnecessary computation 3062591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3072591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (!supported.getLanguage().equals(desired.getLanguage())) { 3082591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return 0; 3092591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3101c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader if (isPseudoLocale(supported) || isPseudoLocale(desired)) { 3111c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // The locales are not the same, but the languages are the same, and one of the locales 3121c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // is a pseudo-locale. So this is not a match. 3131c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return 0; 3141c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 3152591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader final String supportedScr = getLikelyScript(supported); 316b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader if (supportedScr.isEmpty()) { 317b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // If we can't guess a script, we don't know enough about the locales' language to find 318b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // if the locales match. So we fall back to old behavior of matching, which considered 319b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // locales with different regions different. 320b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader final String supportedRegion = supported.getCountry(); 321b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader return (supportedRegion.isEmpty() || 322b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader supportedRegion.equals(desired.getCountry())) 323b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader ? 1 : 0; 324b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader } 3252591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader final String desiredScr = getLikelyScript(desired); 326b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // There is no match if the two locales use different scripts. This will most imporantly 327b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // take care of traditional vs simplified Chinese. 3282591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return supportedScr.equals(desiredScr) ? 1 : 0; 3292591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3302591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader 331834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private int findFirstMatchIndex(Locale supportedLocale) { 332834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader for (int idx = 0; idx < mList.length; idx++) { 333834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final int score = matchScore(supportedLocale, mList[idx]); 334834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (score > 0) { 335834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return idx; 336834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 337834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 338834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return Integer.MAX_VALUE; 339834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 340834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 341fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader private static final Locale EN_LATN = Locale.forLanguageTag("en-Latn"); 342fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader 343834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private int computeFirstMatchIndex(Collection<String> supportedLocales, 344834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader boolean assumeEnglishIsSupported) { 3452591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (mList.length == 1) { // just one locale, perhaps the most common scenario 346834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 3472591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3482591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (mList.length == 0) { // empty locale list 349834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return -1; 3502591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 351834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 3522591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader int bestIndex = Integer.MAX_VALUE; 353834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // Try English first, so we can return early if it's in the LocaleList 354834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (assumeEnglishIsSupported) { 355834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final int idx = findFirstMatchIndex(EN_LATN); 356834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (idx == 0) { // We have a match on the first locale, which is good enough 357834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 358834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } else if (idx < bestIndex) { 359834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader bestIndex = idx; 360fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader } 361834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 362834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader for (String languageTag : supportedLocales) { 363834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final Locale supportedLocale = Locale.forLanguageTag(languageTag); 3642591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader // We expect the average length of locale lists used for locale resolution to be 3652591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader // smaller than three, so it's OK to do this as an O(mn) algorithm. 366834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final int idx = findFirstMatchIndex(supportedLocale); 367834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (idx == 0) { // We have a match on the first locale, which is good enough 368834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 369834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } else if (idx < bestIndex) { 370834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader bestIndex = idx; 3712591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3722591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 373834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (bestIndex == Integer.MAX_VALUE) { 374834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // no match was found, so we fall back to the first locale in the locale list 375834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 3762591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } else { 377834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return bestIndex; 3782591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3798bca69858aefd2326b1bb7ead75796778ed54e93Roozbeh Pournader } 3808bca69858aefd2326b1bb7ead75796778ed54e93Roozbeh Pournader 381834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private Locale computeFirstMatch(Collection<String> supportedLocales, 382834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader boolean assumeEnglishIsSupported) { 383834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader int bestIndex = computeFirstMatchIndex(supportedLocales, assumeEnglishIsSupported); 384834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return bestIndex == -1 ? null : mList[bestIndex]; 385834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 386834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 3871c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader /** 388fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * Returns the first match in the locale list given an unordered array of supported locales 389834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * in BCP 47 format. 390fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * 39166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return The first {@link Locale} from this list that appears in the given array, or 39266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * {@code null} if the {@link LocaleList} is empty. 393fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader */ 394fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader @Nullable 395fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader public Locale getFirstMatch(String[] supportedLocales) { 396834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return computeFirstMatch(Arrays.asList(supportedLocales), 397834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader false /* assume English is not supported */); 398fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader } 399fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader 400fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader /** 401b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski * {@hide} 402b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski */ 403b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski public int getFirstMatchIndex(String[] supportedLocales) { 404b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski return computeFirstMatchIndex(Arrays.asList(supportedLocales), 405b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski false /* assume English is not supported */); 406b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski } 407b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski 408b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski /** 409fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * Same as getFirstMatch(), but with English assumed to be supported, even if it's not. 410fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * {@hide} 411fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader */ 412fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader @Nullable 413fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader public Locale getFirstMatchWithEnglishSupported(String[] supportedLocales) { 414834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return computeFirstMatch(Arrays.asList(supportedLocales), 415834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader true /* assume English is supported */); 416834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 417834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 418834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader /** 419834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * {@hide} 420834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader */ 421834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader public int getFirstMatchIndexWithEnglishSupported(Collection<String> supportedLocales) { 422834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return computeFirstMatchIndex(supportedLocales, true /* assume English is supported */); 423fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader } 424fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader 425fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader /** 426834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * {@hide} 427834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader */ 428834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader public int getFirstMatchIndexWithEnglishSupported(String[] supportedLocales) { 429834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return getFirstMatchIndexWithEnglishSupported(Arrays.asList(supportedLocales)); 430834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 431834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 432834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader /** 433834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * Returns true if the collection of locale tags only contains empty locales and pseudolocales. 4341c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader * Assumes that there is no repetition in the input. 4351c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader * {@hide} 4361c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader */ 437b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski public static boolean isPseudoLocalesOnly(@Nullable String[] supportedLocales) { 438b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski if (supportedLocales == null) { 439b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski return true; 440b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski } 441b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski 4421c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) { 4431c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // This is for optimization. Since there's no repetition in the input, if we have more 4441c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // than the number of pseudo-locales plus one for the empty string, it's guaranteed 445834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // that we have some meaninful locale in the collection, so the list is not "practically 4461c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // empty". 4471c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return false; 4481c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4491c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader for (String locale : supportedLocales) { 4501c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader if (!locale.isEmpty() && !isPseudoLocale(locale)) { 4511c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return false; 4521c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4531c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4541c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return true; 4551c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4561c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 457a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader private final static Object sLock = new Object(); 458a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 459a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader @GuardedBy("sLock") 4602b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader private static LocaleList sLastExplicitlySetLocaleList = null; 4612b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @GuardedBy("sLock") 4622b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader private static LocaleList sDefaultLocaleList = null; 4632b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @GuardedBy("sLock") 464834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private static LocaleList sDefaultAdjustedLocaleList = null; 465834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader @GuardedBy("sLock") 4662b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader private static Locale sLastDefaultLocale = null; 467a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 4682b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 4692b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but 4702b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * not necessarily at the top of the list. The default locale not being at the top of the list 4712b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * is an indication that the system has set the default locale to one of the user's other 4722b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * preferred locales, having concluded that the primary preference is not supported but a 4732b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * secondary preference is. 4742b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 47566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * <p>Note that the default LocaleList would change if Locale.setDefault() is called. This 47666f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * method takes that into account by always checking the output of Locale.getDefault() and 47766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * recalculating the default LocaleList if needed.</p> 4782b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 479a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader @NonNull @Size(min=1) 480a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader public static LocaleList getDefault() { 4812b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final Locale defaultLocale = Locale.getDefault(); 482a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader synchronized (sLock) { 4832b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (!defaultLocale.equals(sLastDefaultLocale)) { 4842b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sLastDefaultLocale = defaultLocale; 4852b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // It's either the first time someone has asked for the default locale list, or 4862b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // someone has called Locale.setDefault() since we last set or adjusted the default 487834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // locale list. So let's recalculate the locale list. 4882b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (sDefaultLocaleList != null 489fee44846376c212114223fc4259382921e6dca7aRoozbeh Pournader && defaultLocale.equals(sDefaultLocaleList.get(0))) { 4902b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // The default Locale has changed, but it happens to be the first locale in the 4912b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // default locale list, so we don't need to construct a new locale list. 4922b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return sDefaultLocaleList; 4932b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 4942b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sDefaultLocaleList = new LocaleList(defaultLocale, sLastExplicitlySetLocaleList); 495834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sDefaultAdjustedLocaleList = sDefaultLocaleList; 496a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader } 4972b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // sDefaultLocaleList can't be null, since it can't be set to null by 4982b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // LocaleList.setDefault(), and if getDefault() is called before a call to 4992b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // setDefault(), sLastDefaultLocale would be null and the check above would set 5002b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // sDefaultLocaleList. 5012b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return sDefaultLocaleList; 5022b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5032b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5042b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 5052b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 506834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * Returns the default locale list, adjusted by moving the default locale to its first 507834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * position. 508834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader */ 509834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader @NonNull @Size(min=1) 510834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader public static LocaleList getAdjustedDefault() { 511834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader getDefault(); // to recalculate the default locale list, if necessary 512834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader synchronized (sLock) { 513834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return sDefaultAdjustedLocaleList; 514834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 515834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 516834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 517834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader /** 5182b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * Also sets the default locale by calling Locale.setDefault() with the first locale in the 5192b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * list. 5202b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 5212b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * @throws NullPointerException if the input is <code>null</code>. 5222b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * @throws IllegalArgumentException if the input is empty. 5232b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 5242b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public static void setDefault(@NonNull @Size(min=1) LocaleList locales) { 5252b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader setDefault(locales, 0); 5262b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5272b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 5282b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 5292b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * This may be used directly by system processes to set the default locale list for apps. For 5302b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * such uses, the default locale list would always come from the user preferences, but the 5312b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * default locale may have been chosen to be a locale other than the first locale in the locale 5322b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * list (based on the locales the app supports). 5332b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 5342b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * {@hide} 5352b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 5362b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public static void setDefault(@NonNull @Size(min=1) LocaleList locales, int localeIndex) { 5372b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (locales == null) { 5382b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new NullPointerException("locales is null"); 5392b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5402b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (locales.isEmpty()) { 5412b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new IllegalArgumentException("locales is empty"); 5422b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5432b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader synchronized (sLock) { 5442b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sLastDefaultLocale = locales.get(localeIndex); 5452b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader Locale.setDefault(sLastDefaultLocale); 5462b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sLastExplicitlySetLocaleList = locales; 5472b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sDefaultLocaleList = locales; 548834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (localeIndex == 0) { 549834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sDefaultAdjustedLocaleList = sDefaultLocaleList; 550834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } else { 551834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sDefaultAdjustedLocaleList = new LocaleList( 552834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sLastDefaultLocale, sDefaultLocaleList); 553834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 554a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader } 555a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader } 5560ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader} 557